video.js 1.9 MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680386813868238683386843868538686386873868838689386903869138692386933869438695386963869738698386993870038701387023870338704387053870638707387083870938710387113871238713387143871538716387173871838719387203872138722387233872438725387263872738728387293873038731387323873338734387353873638737387383873938740387413874238743387443874538746387473874838749387503875138752387533875438755387563875738758387593876038761387623876338764387653876638767387683876938770387713877238773387743877538776387773877838779387803878138782387833878438785387863878738788387893879038791387923879338794387953879638797387983879938800388013880238803388043880538806388073880838809388103881138812388133881438815388163881738818388193882038821388223882338824388253882638827388283882938830388313883238833388343883538836388373883838839388403884138842388433884438845388463884738848388493885038851388523885338854388553885638857388583885938860388613886238863388643886538866388673886838869388703887138872388733887438875388763887738878388793888038881388823888338884388853888638887388883888938890388913889238893388943889538896388973889838899389003890138902389033890438905389063890738908389093891038911389123891338914389153891638917389183891938920389213892238923389243892538926389273892838929389303893138932389333893438935389363893738938389393894038941389423894338944389453894638947389483894938950389513895238953389543895538956389573895838959389603896138962389633896438965389663896738968389693897038971389723897338974389753897638977389783897938980389813898238983389843898538986389873898838989389903899138992389933899438995389963899738998389993900039001390023900339004390053900639007390083900939010390113901239013390143901539016390173901839019390203902139022390233902439025390263902739028390293903039031390323903339034390353903639037390383903939040390413904239043390443904539046390473904839049390503905139052390533905439055390563905739058390593906039061390623906339064390653906639067390683906939070390713907239073390743907539076390773907839079390803908139082390833908439085390863908739088390893909039091390923909339094390953909639097390983909939100391013910239103391043910539106391073910839109391103911139112391133911439115391163911739118391193912039121391223912339124391253912639127391283912939130391313913239133391343913539136391373913839139391403914139142391433914439145391463914739148391493915039151391523915339154391553915639157391583915939160391613916239163391643916539166391673916839169391703917139172391733917439175391763917739178391793918039181391823918339184391853918639187391883918939190391913919239193391943919539196391973919839199392003920139202392033920439205392063920739208392093921039211392123921339214392153921639217392183921939220392213922239223392243922539226392273922839229392303923139232392333923439235392363923739238392393924039241392423924339244392453924639247392483924939250392513925239253392543925539256392573925839259392603926139262392633926439265392663926739268392693927039271392723927339274392753927639277392783927939280392813928239283392843928539286392873928839289392903929139292392933929439295392963929739298392993930039301393023930339304393053930639307393083930939310393113931239313393143931539316393173931839319393203932139322393233932439325393263932739328393293933039331393323933339334393353933639337393383933939340393413934239343393443934539346393473934839349393503935139352393533935439355393563935739358393593936039361393623936339364393653936639367393683936939370393713937239373393743937539376393773937839379393803938139382393833938439385393863938739388393893939039391393923939339394393953939639397393983939939400394013940239403394043940539406394073940839409394103941139412394133941439415394163941739418394193942039421394223942339424394253942639427394283942939430394313943239433394343943539436394373943839439394403944139442394433944439445394463944739448394493945039451394523945339454394553945639457394583945939460394613946239463394643946539466394673946839469394703947139472394733947439475394763947739478394793948039481394823948339484394853948639487394883948939490394913949239493394943949539496394973949839499395003950139502395033950439505395063950739508395093951039511395123951339514395153951639517395183951939520395213952239523395243952539526395273952839529395303953139532395333953439535395363953739538395393954039541395423954339544395453954639547395483954939550395513955239553395543955539556395573955839559395603956139562395633956439565395663956739568395693957039571395723957339574395753957639577395783957939580395813958239583395843958539586395873958839589395903959139592395933959439595395963959739598395993960039601396023960339604396053960639607396083960939610396113961239613396143961539616396173961839619396203962139622396233962439625396263962739628396293963039631396323963339634396353963639637396383963939640396413964239643396443964539646396473964839649396503965139652396533965439655396563965739658396593966039661396623966339664396653966639667396683966939670396713967239673396743967539676396773967839679396803968139682396833968439685396863968739688396893969039691396923969339694396953969639697396983969939700397013970239703397043970539706397073970839709397103971139712397133971439715397163971739718397193972039721397223972339724397253972639727397283972939730397313973239733397343973539736397373973839739397403974139742397433974439745397463974739748397493975039751397523975339754397553975639757397583975939760397613976239763397643976539766397673976839769397703977139772397733977439775397763977739778397793978039781397823978339784397853978639787397883978939790397913979239793397943979539796397973979839799398003980139802398033980439805398063980739808398093981039811398123981339814398153981639817398183981939820398213982239823398243982539826398273982839829398303983139832398333983439835398363983739838398393984039841398423984339844398453984639847398483984939850398513985239853398543985539856398573985839859398603986139862398633986439865398663986739868398693987039871398723987339874398753987639877398783987939880398813988239883398843988539886398873988839889398903989139892398933989439895398963989739898398993990039901399023990339904399053990639907399083990939910399113991239913399143991539916399173991839919399203992139922399233992439925399263992739928399293993039931399323993339934399353993639937399383993939940399413994239943399443994539946399473994839949399503995139952399533995439955399563995739958399593996039961399623996339964399653996639967399683996939970399713997239973399743997539976399773997839979399803998139982399833998439985399863998739988399893999039991399923999339994399953999639997399983999940000400014000240003400044000540006400074000840009400104001140012400134001440015400164001740018400194002040021400224002340024400254002640027400284002940030400314003240033400344003540036400374003840039400404004140042400434004440045400464004740048400494005040051400524005340054400554005640057400584005940060400614006240063400644006540066400674006840069400704007140072400734007440075400764007740078400794008040081400824008340084400854008640087400884008940090400914009240093400944009540096400974009840099401004010140102401034010440105401064010740108401094011040111401124011340114401154011640117401184011940120401214012240123401244012540126401274012840129401304013140132401334013440135401364013740138401394014040141401424014340144401454014640147401484014940150401514015240153401544015540156401574015840159401604016140162401634016440165401664016740168401694017040171401724017340174401754017640177401784017940180401814018240183401844018540186401874018840189401904019140192401934019440195401964019740198401994020040201402024020340204402054020640207402084020940210402114021240213402144021540216402174021840219402204022140222402234022440225402264022740228402294023040231402324023340234402354023640237402384023940240402414024240243402444024540246402474024840249402504025140252402534025440255402564025740258402594026040261402624026340264402654026640267402684026940270402714027240273402744027540276402774027840279402804028140282402834028440285402864028740288402894029040291402924029340294402954029640297402984029940300403014030240303403044030540306403074030840309403104031140312403134031440315403164031740318403194032040321403224032340324403254032640327403284032940330403314033240333403344033540336403374033840339403404034140342403434034440345403464034740348403494035040351403524035340354403554035640357403584035940360403614036240363403644036540366403674036840369403704037140372403734037440375403764037740378403794038040381403824038340384403854038640387403884038940390403914039240393403944039540396403974039840399404004040140402404034040440405404064040740408404094041040411404124041340414404154041640417404184041940420404214042240423404244042540426404274042840429404304043140432404334043440435404364043740438404394044040441404424044340444404454044640447404484044940450404514045240453404544045540456404574045840459404604046140462404634046440465404664046740468404694047040471404724047340474404754047640477404784047940480404814048240483404844048540486404874048840489404904049140492404934049440495404964049740498404994050040501405024050340504405054050640507405084050940510405114051240513405144051540516405174051840519405204052140522405234052440525405264052740528405294053040531405324053340534405354053640537405384053940540405414054240543405444054540546405474054840549405504055140552405534055440555405564055740558405594056040561405624056340564405654056640567405684056940570405714057240573405744057540576405774057840579405804058140582405834058440585405864058740588405894059040591405924059340594405954059640597405984059940600406014060240603406044060540606406074060840609406104061140612406134061440615406164061740618406194062040621406224062340624406254062640627406284062940630406314063240633406344063540636406374063840639406404064140642406434064440645406464064740648406494065040651406524065340654406554065640657406584065940660406614066240663406644066540666406674066840669406704067140672406734067440675406764067740678406794068040681406824068340684406854068640687406884068940690406914069240693406944069540696406974069840699407004070140702407034070440705407064070740708407094071040711407124071340714407154071640717407184071940720407214072240723407244072540726407274072840729407304073140732407334073440735407364073740738407394074040741407424074340744407454074640747407484074940750407514075240753407544075540756407574075840759407604076140762407634076440765407664076740768407694077040771407724077340774407754077640777407784077940780407814078240783407844078540786407874078840789407904079140792407934079440795407964079740798407994080040801408024080340804408054080640807408084080940810408114081240813408144081540816408174081840819408204082140822408234082440825408264082740828408294083040831408324083340834408354083640837408384083940840408414084240843408444084540846408474084840849408504085140852408534085440855408564085740858408594086040861408624086340864408654086640867408684086940870408714087240873408744087540876408774087840879408804088140882408834088440885408864088740888408894089040891408924089340894408954089640897408984089940900409014090240903409044090540906409074090840909409104091140912409134091440915409164091740918409194092040921409224092340924409254092640927409284092940930409314093240933409344093540936409374093840939409404094140942409434094440945409464094740948409494095040951409524095340954409554095640957409584095940960409614096240963409644096540966409674096840969409704097140972409734097440975409764097740978409794098040981409824098340984409854098640987409884098940990409914099240993409944099540996409974099840999410004100141002410034100441005410064100741008410094101041011410124101341014410154101641017410184101941020410214102241023410244102541026410274102841029410304103141032410334103441035410364103741038410394104041041410424104341044410454104641047410484104941050410514105241053410544105541056410574105841059410604106141062410634106441065410664106741068410694107041071410724107341074410754107641077410784107941080410814108241083410844108541086410874108841089410904109141092410934109441095410964109741098410994110041101411024110341104411054110641107411084110941110411114111241113411144111541116411174111841119411204112141122411234112441125411264112741128411294113041131411324113341134411354113641137411384113941140411414114241143411444114541146411474114841149411504115141152411534115441155411564115741158411594116041161411624116341164411654116641167411684116941170411714117241173411744117541176411774117841179411804118141182411834118441185411864118741188411894119041191411924119341194411954119641197411984119941200412014120241203412044120541206412074120841209412104121141212412134121441215412164121741218412194122041221412224122341224412254122641227412284122941230412314123241233412344123541236412374123841239412404124141242412434124441245412464124741248412494125041251412524125341254412554125641257412584125941260412614126241263412644126541266412674126841269412704127141272412734127441275412764127741278412794128041281412824128341284412854128641287412884128941290412914129241293412944129541296412974129841299413004130141302413034130441305413064130741308413094131041311413124131341314413154131641317413184131941320413214132241323413244132541326413274132841329413304133141332413334133441335413364133741338413394134041341413424134341344413454134641347413484134941350413514135241353413544135541356413574135841359413604136141362413634136441365413664136741368413694137041371413724137341374413754137641377413784137941380413814138241383413844138541386413874138841389413904139141392413934139441395413964139741398413994140041401414024140341404414054140641407414084140941410414114141241413414144141541416414174141841419414204142141422414234142441425414264142741428414294143041431414324143341434414354143641437414384143941440414414144241443414444144541446414474144841449414504145141452414534145441455414564145741458414594146041461414624146341464414654146641467414684146941470414714147241473414744147541476414774147841479414804148141482414834148441485414864148741488414894149041491414924149341494414954149641497414984149941500415014150241503415044150541506415074150841509415104151141512415134151441515415164151741518415194152041521415224152341524415254152641527415284152941530415314153241533415344153541536415374153841539415404154141542415434154441545415464154741548415494155041551415524155341554415554155641557415584155941560415614156241563415644156541566415674156841569415704157141572415734157441575415764157741578415794158041581415824158341584415854158641587415884158941590415914159241593415944159541596415974159841599416004160141602416034160441605416064160741608416094161041611416124161341614416154161641617416184161941620416214162241623416244162541626416274162841629416304163141632416334163441635416364163741638416394164041641416424164341644416454164641647416484164941650416514165241653416544165541656416574165841659416604166141662416634166441665416664166741668416694167041671416724167341674416754167641677416784167941680416814168241683416844168541686416874168841689416904169141692416934169441695416964169741698416994170041701417024170341704417054170641707417084170941710417114171241713417144171541716417174171841719417204172141722417234172441725417264172741728417294173041731417324173341734417354173641737417384173941740417414174241743417444174541746417474174841749417504175141752417534175441755417564175741758417594176041761417624176341764417654176641767417684176941770417714177241773417744177541776417774177841779417804178141782417834178441785417864178741788417894179041791417924179341794417954179641797417984179941800418014180241803418044180541806418074180841809418104181141812418134181441815418164181741818418194182041821418224182341824418254182641827418284182941830418314183241833418344183541836418374183841839418404184141842418434184441845418464184741848418494185041851418524185341854418554185641857418584185941860418614186241863418644186541866418674186841869418704187141872418734187441875418764187741878418794188041881418824188341884418854188641887418884188941890418914189241893418944189541896418974189841899419004190141902419034190441905419064190741908419094191041911419124191341914419154191641917419184191941920419214192241923419244192541926419274192841929419304193141932419334193441935419364193741938419394194041941419424194341944419454194641947419484194941950419514195241953419544195541956419574195841959419604196141962419634196441965419664196741968419694197041971419724197341974419754197641977419784197941980419814198241983419844198541986419874198841989419904199141992419934199441995419964199741998419994200042001420024200342004420054200642007420084200942010420114201242013420144201542016420174201842019420204202142022420234202442025420264202742028420294203042031420324203342034420354203642037420384203942040420414204242043420444204542046420474204842049420504205142052420534205442055420564205742058420594206042061420624206342064420654206642067420684206942070420714207242073420744207542076420774207842079420804208142082420834208442085420864208742088420894209042091420924209342094420954209642097420984209942100421014210242103421044210542106421074210842109421104211142112421134211442115421164211742118421194212042121421224212342124421254212642127421284212942130421314213242133421344213542136421374213842139421404214142142421434214442145421464214742148421494215042151421524215342154421554215642157421584215942160421614216242163421644216542166421674216842169421704217142172421734217442175421764217742178421794218042181421824218342184421854218642187421884218942190421914219242193421944219542196421974219842199422004220142202422034220442205422064220742208422094221042211422124221342214422154221642217422184221942220422214222242223422244222542226422274222842229422304223142232422334223442235422364223742238422394224042241422424224342244422454224642247422484224942250422514225242253422544225542256422574225842259422604226142262422634226442265422664226742268422694227042271422724227342274422754227642277422784227942280422814228242283422844228542286422874228842289422904229142292422934229442295422964229742298422994230042301423024230342304423054230642307423084230942310423114231242313423144231542316423174231842319423204232142322423234232442325423264232742328423294233042331423324233342334423354233642337423384233942340423414234242343423444234542346423474234842349423504235142352423534235442355423564235742358423594236042361423624236342364423654236642367423684236942370423714237242373423744237542376423774237842379423804238142382423834238442385423864238742388423894239042391423924239342394423954239642397423984239942400424014240242403424044240542406424074240842409424104241142412424134241442415424164241742418424194242042421424224242342424424254242642427424284242942430424314243242433424344243542436424374243842439424404244142442424434244442445424464244742448424494245042451424524245342454424554245642457424584245942460424614246242463424644246542466424674246842469424704247142472424734247442475424764247742478424794248042481424824248342484424854248642487424884248942490424914249242493424944249542496424974249842499425004250142502425034250442505425064250742508425094251042511425124251342514425154251642517425184251942520425214252242523425244252542526425274252842529425304253142532425334253442535425364253742538425394254042541425424254342544425454254642547425484254942550425514255242553425544255542556425574255842559425604256142562425634256442565425664256742568425694257042571425724257342574425754257642577425784257942580425814258242583425844258542586425874258842589425904259142592425934259442595425964259742598425994260042601426024260342604426054260642607426084260942610426114261242613426144261542616426174261842619426204262142622426234262442625426264262742628426294263042631426324263342634426354263642637426384263942640426414264242643426444264542646426474264842649426504265142652426534265442655426564265742658426594266042661426624266342664426654266642667426684266942670426714267242673426744267542676426774267842679426804268142682426834268442685426864268742688426894269042691426924269342694426954269642697426984269942700427014270242703427044270542706427074270842709427104271142712427134271442715427164271742718427194272042721427224272342724427254272642727427284272942730427314273242733427344273542736427374273842739427404274142742427434274442745427464274742748427494275042751427524275342754427554275642757427584275942760427614276242763427644276542766427674276842769427704277142772427734277442775427764277742778427794278042781427824278342784427854278642787427884278942790427914279242793427944279542796427974279842799428004280142802428034280442805428064280742808428094281042811428124281342814428154281642817428184281942820428214282242823428244282542826428274282842829428304283142832428334283442835428364283742838428394284042841428424284342844428454284642847428484284942850428514285242853428544285542856428574285842859428604286142862428634286442865428664286742868428694287042871428724287342874428754287642877428784287942880428814288242883428844288542886428874288842889428904289142892428934289442895428964289742898428994290042901429024290342904429054290642907429084290942910429114291242913429144291542916429174291842919429204292142922429234292442925429264292742928429294293042931429324293342934429354293642937429384293942940429414294242943429444294542946429474294842949429504295142952429534295442955429564295742958429594296042961429624296342964429654296642967429684296942970429714297242973429744297542976429774297842979429804298142982429834298442985429864298742988429894299042991429924299342994429954299642997429984299943000430014300243003430044300543006430074300843009430104301143012430134301443015430164301743018430194302043021430224302343024430254302643027430284302943030430314303243033430344303543036430374303843039430404304143042430434304443045430464304743048430494305043051430524305343054430554305643057430584305943060430614306243063430644306543066430674306843069430704307143072430734307443075430764307743078430794308043081430824308343084430854308643087430884308943090430914309243093430944309543096430974309843099431004310143102431034310443105431064310743108431094311043111431124311343114431154311643117431184311943120431214312243123431244312543126431274312843129431304313143132431334313443135431364313743138431394314043141431424314343144431454314643147431484314943150431514315243153431544315543156431574315843159431604316143162431634316443165431664316743168431694317043171431724317343174431754317643177431784317943180431814318243183431844318543186431874318843189431904319143192431934319443195431964319743198431994320043201432024320343204432054320643207432084320943210432114321243213432144321543216432174321843219432204322143222432234322443225432264322743228432294323043231432324323343234432354323643237432384323943240432414324243243432444324543246432474324843249432504325143252432534325443255432564325743258432594326043261432624326343264432654326643267432684326943270432714327243273432744327543276432774327843279432804328143282432834328443285432864328743288432894329043291432924329343294432954329643297432984329943300433014330243303433044330543306433074330843309433104331143312433134331443315433164331743318433194332043321433224332343324433254332643327433284332943330433314333243333433344333543336433374333843339433404334143342433434334443345433464334743348433494335043351433524335343354433554335643357433584335943360433614336243363433644336543366433674336843369433704337143372433734337443375433764337743378433794338043381433824338343384433854338643387433884338943390433914339243393433944339543396433974339843399434004340143402434034340443405434064340743408434094341043411434124341343414434154341643417434184341943420434214342243423434244342543426434274342843429434304343143432434334343443435434364343743438434394344043441434424344343444434454344643447434484344943450434514345243453434544345543456434574345843459434604346143462434634346443465434664346743468434694347043471434724347343474434754347643477434784347943480434814348243483434844348543486434874348843489434904349143492434934349443495434964349743498434994350043501435024350343504435054350643507435084350943510435114351243513435144351543516435174351843519435204352143522435234352443525435264352743528435294353043531435324353343534435354353643537435384353943540435414354243543435444354543546435474354843549435504355143552435534355443555435564355743558435594356043561435624356343564435654356643567435684356943570435714357243573435744357543576435774357843579435804358143582435834358443585435864358743588435894359043591435924359343594435954359643597435984359943600436014360243603436044360543606436074360843609436104361143612436134361443615436164361743618436194362043621436224362343624436254362643627436284362943630436314363243633436344363543636436374363843639436404364143642436434364443645436464364743648436494365043651436524365343654436554365643657436584365943660436614366243663436644366543666436674366843669436704367143672436734367443675436764367743678436794368043681436824368343684436854368643687436884368943690436914369243693436944369543696436974369843699437004370143702437034370443705437064370743708437094371043711437124371343714437154371643717437184371943720437214372243723437244372543726437274372843729437304373143732437334373443735437364373743738437394374043741437424374343744437454374643747437484374943750437514375243753437544375543756437574375843759437604376143762437634376443765437664376743768437694377043771437724377343774437754377643777437784377943780437814378243783437844378543786437874378843789437904379143792437934379443795437964379743798437994380043801438024380343804438054380643807438084380943810438114381243813438144381543816438174381843819438204382143822438234382443825438264382743828438294383043831438324383343834438354383643837438384383943840438414384243843438444384543846438474384843849438504385143852438534385443855438564385743858438594386043861438624386343864438654386643867438684386943870438714387243873438744387543876438774387843879438804388143882438834388443885438864388743888438894389043891438924389343894438954389643897438984389943900439014390243903439044390543906439074390843909439104391143912439134391443915439164391743918439194392043921439224392343924439254392643927439284392943930439314393243933439344393543936439374393843939439404394143942439434394443945439464394743948439494395043951439524395343954439554395643957439584395943960439614396243963439644396543966439674396843969439704397143972439734397443975439764397743978439794398043981439824398343984439854398643987439884398943990439914399243993439944399543996439974399843999440004400144002440034400444005440064400744008440094401044011440124401344014440154401644017440184401944020440214402244023440244402544026440274402844029440304403144032440334403444035440364403744038440394404044041440424404344044440454404644047440484404944050440514405244053440544405544056440574405844059440604406144062440634406444065440664406744068440694407044071440724407344074440754407644077440784407944080440814408244083440844408544086440874408844089440904409144092440934409444095440964409744098440994410044101441024410344104441054410644107441084410944110441114411244113441144411544116441174411844119441204412144122441234412444125441264412744128441294413044131441324413344134441354413644137441384413944140441414414244143441444414544146441474414844149441504415144152441534415444155441564415744158441594416044161441624416344164441654416644167441684416944170441714417244173441744417544176441774417844179441804418144182441834418444185441864418744188441894419044191441924419344194441954419644197441984419944200442014420244203442044420544206442074420844209442104421144212442134421444215442164421744218442194422044221442224422344224442254422644227442284422944230442314423244233442344423544236442374423844239442404424144242442434424444245442464424744248442494425044251442524425344254442554425644257442584425944260442614426244263442644426544266442674426844269442704427144272442734427444275442764427744278442794428044281442824428344284442854428644287442884428944290442914429244293442944429544296442974429844299443004430144302443034430444305443064430744308443094431044311443124431344314443154431644317443184431944320443214432244323443244432544326443274432844329443304433144332443334433444335443364433744338443394434044341443424434344344443454434644347443484434944350443514435244353443544435544356443574435844359443604436144362443634436444365443664436744368443694437044371443724437344374443754437644377443784437944380443814438244383443844438544386443874438844389443904439144392443934439444395443964439744398443994440044401444024440344404444054440644407444084440944410444114441244413444144441544416444174441844419444204442144422444234442444425444264442744428444294443044431444324443344434444354443644437444384443944440444414444244443444444444544446444474444844449444504445144452444534445444455444564445744458444594446044461444624446344464444654446644467444684446944470444714447244473444744447544476444774447844479444804448144482444834448444485444864448744488444894449044491444924449344494444954449644497444984449944500445014450244503445044450544506445074450844509445104451144512445134451444515445164451744518445194452044521445224452344524445254452644527445284452944530445314453244533445344453544536445374453844539445404454144542445434454444545445464454744548445494455044551445524455344554445554455644557445584455944560445614456244563445644456544566445674456844569445704457144572445734457444575445764457744578445794458044581445824458344584445854458644587445884458944590445914459244593445944459544596445974459844599446004460144602446034460444605446064460744608446094461044611446124461344614446154461644617446184461944620446214462244623446244462544626446274462844629446304463144632446334463444635446364463744638446394464044641446424464344644446454464644647446484464944650446514465244653446544465544656446574465844659446604466144662446634466444665446664466744668446694467044671446724467344674446754467644677446784467944680446814468244683446844468544686446874468844689446904469144692446934469444695446964469744698446994470044701447024470344704447054470644707447084470944710447114471244713447144471544716447174471844719447204472144722447234472444725447264472744728447294473044731447324473344734447354473644737447384473944740447414474244743447444474544746447474474844749447504475144752447534475444755447564475744758447594476044761447624476344764447654476644767447684476944770447714477244773447744477544776447774477844779447804478144782447834478444785447864478744788447894479044791447924479344794447954479644797447984479944800448014480244803448044480544806448074480844809448104481144812448134481444815448164481744818448194482044821448224482344824448254482644827448284482944830448314483244833448344483544836448374483844839448404484144842448434484444845448464484744848448494485044851448524485344854448554485644857448584485944860448614486244863448644486544866448674486844869448704487144872448734487444875448764487744878448794488044881448824488344884448854488644887448884488944890448914489244893448944489544896448974489844899449004490144902449034490444905449064490744908449094491044911449124491344914449154491644917449184491944920449214492244923449244492544926449274492844929449304493144932449334493444935449364493744938449394494044941449424494344944449454494644947449484494944950449514495244953449544495544956449574495844959449604496144962449634496444965449664496744968449694497044971449724497344974449754497644977449784497944980449814498244983449844498544986449874498844989449904499144992449934499444995449964499744998449994500045001450024500345004450054500645007450084500945010450114501245013450144501545016450174501845019450204502145022450234502445025450264502745028450294503045031450324503345034450354503645037450384503945040450414504245043450444504545046450474504845049450504505145052450534505445055450564505745058450594506045061450624506345064450654506645067450684506945070450714507245073450744507545076450774507845079450804508145082450834508445085450864508745088450894509045091450924509345094450954509645097450984509945100451014510245103451044510545106451074510845109451104511145112451134511445115451164511745118451194512045121451224512345124451254512645127451284512945130451314513245133451344513545136451374513845139451404514145142451434514445145451464514745148451494515045151451524515345154451554515645157451584515945160451614516245163451644516545166451674516845169451704517145172451734517445175451764517745178451794518045181451824518345184451854518645187451884518945190451914519245193451944519545196451974519845199452004520145202452034520445205452064520745208452094521045211452124521345214452154521645217452184521945220452214522245223452244522545226452274522845229452304523145232452334523445235452364523745238452394524045241452424524345244452454524645247452484524945250452514525245253452544525545256452574525845259452604526145262452634526445265452664526745268452694527045271452724527345274452754527645277452784527945280452814528245283452844528545286452874528845289452904529145292452934529445295452964529745298452994530045301453024530345304453054530645307453084530945310453114531245313453144531545316453174531845319453204532145322453234532445325453264532745328453294533045331453324533345334453354533645337453384533945340453414534245343453444534545346453474534845349453504535145352453534535445355453564535745358453594536045361453624536345364453654536645367453684536945370453714537245373453744537545376453774537845379453804538145382453834538445385453864538745388453894539045391453924539345394453954539645397453984539945400454014540245403454044540545406454074540845409454104541145412454134541445415454164541745418454194542045421454224542345424454254542645427454284542945430454314543245433454344543545436454374543845439454404544145442454434544445445454464544745448454494545045451454524545345454454554545645457454584545945460454614546245463454644546545466454674546845469454704547145472454734547445475454764547745478454794548045481454824548345484454854548645487454884548945490454914549245493454944549545496454974549845499455004550145502455034550445505455064550745508455094551045511455124551345514455154551645517455184551945520455214552245523455244552545526455274552845529455304553145532455334553445535455364553745538455394554045541455424554345544455454554645547455484554945550455514555245553455544555545556455574555845559455604556145562455634556445565455664556745568455694557045571455724557345574455754557645577455784557945580455814558245583455844558545586455874558845589455904559145592455934559445595455964559745598455994560045601456024560345604456054560645607456084560945610456114561245613456144561545616456174561845619456204562145622456234562445625456264562745628456294563045631456324563345634456354563645637456384563945640456414564245643456444564545646456474564845649456504565145652456534565445655456564565745658456594566045661456624566345664456654566645667456684566945670456714567245673456744567545676456774567845679456804568145682456834568445685456864568745688456894569045691456924569345694456954569645697456984569945700457014570245703457044570545706457074570845709457104571145712457134571445715457164571745718457194572045721457224572345724457254572645727457284572945730457314573245733457344573545736457374573845739457404574145742457434574445745457464574745748457494575045751457524575345754457554575645757457584575945760457614576245763457644576545766457674576845769457704577145772457734577445775457764577745778457794578045781457824578345784457854578645787457884578945790457914579245793457944579545796457974579845799458004580145802458034580445805458064580745808458094581045811458124581345814458154581645817458184581945820458214582245823458244582545826458274582845829458304583145832458334583445835458364583745838458394584045841458424584345844458454584645847458484584945850458514585245853458544585545856458574585845859458604586145862458634586445865458664586745868458694587045871458724587345874458754587645877458784587945880458814588245883458844588545886458874588845889458904589145892458934589445895458964589745898458994590045901459024590345904459054590645907459084590945910459114591245913459144591545916459174591845919459204592145922459234592445925459264592745928459294593045931459324593345934459354593645937459384593945940459414594245943459444594545946459474594845949459504595145952459534595445955459564595745958459594596045961459624596345964459654596645967459684596945970459714597245973459744597545976459774597845979459804598145982459834598445985459864598745988459894599045991459924599345994459954599645997459984599946000460014600246003460044600546006460074600846009460104601146012460134601446015460164601746018460194602046021460224602346024460254602646027460284602946030460314603246033460344603546036460374603846039460404604146042460434604446045460464604746048460494605046051460524605346054460554605646057460584605946060460614606246063460644606546066460674606846069460704607146072460734607446075460764607746078460794608046081460824608346084460854608646087460884608946090460914609246093460944609546096460974609846099461004610146102461034610446105461064610746108461094611046111461124611346114461154611646117461184611946120461214612246123461244612546126461274612846129461304613146132461334613446135461364613746138461394614046141461424614346144461454614646147461484614946150461514615246153461544615546156461574615846159461604616146162461634616446165461664616746168461694617046171461724617346174461754617646177461784617946180461814618246183461844618546186461874618846189461904619146192461934619446195461964619746198461994620046201462024620346204462054620646207462084620946210462114621246213462144621546216462174621846219462204622146222462234622446225462264622746228462294623046231462324623346234462354623646237462384623946240462414624246243462444624546246462474624846249462504625146252462534625446255462564625746258462594626046261462624626346264462654626646267462684626946270462714627246273462744627546276462774627846279462804628146282462834628446285462864628746288462894629046291462924629346294462954629646297462984629946300463014630246303463044630546306463074630846309463104631146312463134631446315463164631746318463194632046321463224632346324463254632646327463284632946330463314633246333463344633546336463374633846339463404634146342463434634446345463464634746348463494635046351463524635346354463554635646357463584635946360463614636246363463644636546366463674636846369463704637146372463734637446375463764637746378463794638046381463824638346384463854638646387463884638946390463914639246393463944639546396463974639846399464004640146402464034640446405464064640746408464094641046411464124641346414464154641646417464184641946420464214642246423464244642546426464274642846429464304643146432464334643446435464364643746438464394644046441464424644346444464454644646447464484644946450464514645246453464544645546456464574645846459464604646146462464634646446465464664646746468464694647046471464724647346474464754647646477464784647946480464814648246483464844648546486464874648846489464904649146492464934649446495464964649746498464994650046501465024650346504465054650646507465084650946510465114651246513465144651546516465174651846519465204652146522465234652446525465264652746528465294653046531465324653346534465354653646537465384653946540465414654246543465444654546546465474654846549465504655146552465534655446555465564655746558465594656046561465624656346564465654656646567465684656946570465714657246573465744657546576465774657846579465804658146582465834658446585465864658746588465894659046591465924659346594465954659646597465984659946600466014660246603466044660546606466074660846609466104661146612466134661446615466164661746618466194662046621466224662346624466254662646627466284662946630466314663246633466344663546636466374663846639466404664146642466434664446645466464664746648466494665046651466524665346654466554665646657466584665946660466614666246663466644666546666466674666846669466704667146672466734667446675466764667746678466794668046681466824668346684466854668646687466884668946690466914669246693466944669546696466974669846699467004670146702467034670446705467064670746708467094671046711467124671346714467154671646717467184671946720467214672246723467244672546726467274672846729467304673146732467334673446735467364673746738467394674046741467424674346744467454674646747467484674946750467514675246753467544675546756467574675846759467604676146762467634676446765467664676746768467694677046771467724677346774467754677646777467784677946780467814678246783467844678546786467874678846789467904679146792467934679446795467964679746798467994680046801468024680346804468054680646807468084680946810468114681246813468144681546816468174681846819468204682146822468234682446825468264682746828468294683046831468324683346834468354683646837468384683946840468414684246843468444684546846468474684846849468504685146852468534685446855468564685746858468594686046861468624686346864468654686646867468684686946870468714687246873468744687546876468774687846879468804688146882468834688446885468864688746888468894689046891468924689346894468954689646897468984689946900469014690246903469044690546906469074690846909469104691146912469134691446915469164691746918469194692046921469224692346924469254692646927469284692946930469314693246933469344693546936469374693846939469404694146942469434694446945469464694746948469494695046951469524695346954469554695646957469584695946960469614696246963469644696546966469674696846969469704697146972469734697446975469764697746978469794698046981469824698346984469854698646987469884698946990469914699246993469944699546996469974699846999470004700147002470034700447005470064700747008470094701047011470124701347014470154701647017470184701947020470214702247023470244702547026470274702847029470304703147032470334703447035470364703747038470394704047041470424704347044470454704647047470484704947050470514705247053470544705547056470574705847059470604706147062470634706447065470664706747068470694707047071470724707347074470754707647077470784707947080470814708247083470844708547086470874708847089470904709147092470934709447095470964709747098470994710047101471024710347104471054710647107471084710947110471114711247113471144711547116471174711847119471204712147122471234712447125471264712747128471294713047131471324713347134471354713647137471384713947140471414714247143471444714547146471474714847149471504715147152471534715447155471564715747158471594716047161471624716347164471654716647167471684716947170471714717247173471744717547176471774717847179471804718147182471834718447185471864718747188471894719047191471924719347194471954719647197471984719947200472014720247203472044720547206472074720847209472104721147212472134721447215472164721747218472194722047221472224722347224472254722647227472284722947230472314723247233472344723547236472374723847239472404724147242472434724447245472464724747248472494725047251472524725347254472554725647257472584725947260472614726247263472644726547266472674726847269472704727147272472734727447275472764727747278472794728047281472824728347284472854728647287472884728947290472914729247293472944729547296472974729847299473004730147302473034730447305473064730747308473094731047311473124731347314473154731647317473184731947320473214732247323473244732547326473274732847329473304733147332473334733447335473364733747338473394734047341473424734347344473454734647347473484734947350473514735247353473544735547356473574735847359473604736147362473634736447365473664736747368473694737047371473724737347374473754737647377473784737947380473814738247383473844738547386473874738847389473904739147392473934739447395473964739747398473994740047401474024740347404474054740647407474084740947410474114741247413474144741547416474174741847419474204742147422474234742447425474264742747428474294743047431474324743347434474354743647437474384743947440474414744247443474444744547446474474744847449474504745147452474534745447455474564745747458474594746047461474624746347464474654746647467474684746947470474714747247473474744747547476474774747847479474804748147482474834748447485474864748747488474894749047491474924749347494474954749647497474984749947500475014750247503475044750547506475074750847509475104751147512475134751447515475164751747518475194752047521475224752347524475254752647527475284752947530475314753247533475344753547536475374753847539475404754147542475434754447545475464754747548475494755047551475524755347554475554755647557475584755947560475614756247563475644756547566475674756847569475704757147572475734757447575475764757747578475794758047581475824758347584475854758647587475884758947590475914759247593475944759547596475974759847599476004760147602476034760447605476064760747608476094761047611476124761347614476154761647617476184761947620476214762247623476244762547626476274762847629476304763147632476334763447635476364763747638476394764047641476424764347644476454764647647476484764947650476514765247653476544765547656476574765847659476604766147662476634766447665476664766747668476694767047671476724767347674476754767647677476784767947680476814768247683476844768547686476874768847689476904769147692476934769447695476964769747698476994770047701477024770347704477054770647707477084770947710477114771247713477144771547716477174771847719477204772147722477234772447725477264772747728477294773047731477324773347734477354773647737477384773947740477414774247743477444774547746477474774847749477504775147752477534775447755477564775747758477594776047761477624776347764477654776647767477684776947770477714777247773477744777547776477774777847779477804778147782477834778447785477864778747788477894779047791477924779347794477954779647797477984779947800478014780247803478044780547806478074780847809478104781147812478134781447815478164781747818478194782047821478224782347824478254782647827478284782947830478314783247833478344783547836478374783847839478404784147842478434784447845478464784747848478494785047851478524785347854478554785647857478584785947860478614786247863478644786547866478674786847869478704787147872478734787447875478764787747878478794788047881478824788347884478854788647887478884788947890478914789247893478944789547896478974789847899479004790147902479034790447905479064790747908479094791047911479124791347914479154791647917479184791947920479214792247923479244792547926479274792847929479304793147932479334793447935479364793747938479394794047941479424794347944479454794647947479484794947950479514795247953479544795547956479574795847959479604796147962479634796447965479664796747968479694797047971479724797347974479754797647977479784797947980479814798247983479844798547986479874798847989479904799147992479934799447995479964799747998479994800048001480024800348004480054800648007480084800948010480114801248013480144801548016480174801848019480204802148022480234802448025480264802748028480294803048031480324803348034480354803648037480384803948040480414804248043480444804548046480474804848049480504805148052480534805448055480564805748058480594806048061480624806348064480654806648067480684806948070480714807248073480744807548076480774807848079480804808148082480834808448085480864808748088480894809048091480924809348094480954809648097480984809948100481014810248103481044810548106481074810848109481104811148112481134811448115481164811748118481194812048121481224812348124481254812648127481284812948130481314813248133481344813548136481374813848139481404814148142481434814448145481464814748148481494815048151481524815348154481554815648157481584815948160481614816248163481644816548166481674816848169481704817148172481734817448175481764817748178481794818048181481824818348184481854818648187481884818948190481914819248193481944819548196481974819848199482004820148202482034820448205482064820748208482094821048211482124821348214482154821648217482184821948220482214822248223482244822548226482274822848229482304823148232482334823448235482364823748238482394824048241482424824348244482454824648247482484824948250482514825248253482544825548256482574825848259482604826148262482634826448265482664826748268482694827048271482724827348274482754827648277482784827948280482814828248283482844828548286482874828848289482904829148292482934829448295482964829748298482994830048301483024830348304483054830648307483084830948310483114831248313483144831548316483174831848319483204832148322483234832448325483264832748328483294833048331483324833348334483354833648337483384833948340483414834248343483444834548346483474834848349483504835148352483534835448355483564835748358483594836048361483624836348364483654836648367483684836948370483714837248373483744837548376483774837848379483804838148382483834838448385483864838748388483894839048391483924839348394483954839648397483984839948400484014840248403484044840548406484074840848409484104841148412484134841448415484164841748418484194842048421484224842348424484254842648427484284842948430484314843248433484344843548436484374843848439484404844148442484434844448445484464844748448484494845048451484524845348454484554845648457484584845948460484614846248463484644846548466484674846848469484704847148472484734847448475484764847748478484794848048481484824848348484484854848648487484884848948490484914849248493484944849548496484974849848499485004850148502485034850448505485064850748508485094851048511485124851348514485154851648517485184851948520485214852248523485244852548526485274852848529485304853148532485334853448535485364853748538485394854048541485424854348544485454854648547485484854948550485514855248553485544855548556485574855848559485604856148562485634856448565485664856748568485694857048571485724857348574485754857648577485784857948580485814858248583485844858548586485874858848589485904859148592485934859448595485964859748598485994860048601486024860348604486054860648607486084860948610486114861248613486144861548616486174861848619486204862148622486234862448625486264862748628486294863048631486324863348634486354863648637486384863948640486414864248643486444864548646486474864848649486504865148652486534865448655486564865748658486594866048661486624866348664486654866648667486684866948670486714867248673486744867548676486774867848679486804868148682486834868448685486864868748688486894869048691486924869348694486954869648697486984869948700487014870248703487044870548706487074870848709487104871148712487134871448715487164871748718487194872048721487224872348724487254872648727487284872948730487314873248733487344873548736487374873848739487404874148742487434874448745487464874748748487494875048751487524875348754487554875648757487584875948760487614876248763487644876548766487674876848769487704877148772487734877448775487764877748778487794878048781487824878348784487854878648787487884878948790487914879248793487944879548796487974879848799488004880148802488034880448805488064880748808488094881048811488124881348814488154881648817488184881948820488214882248823488244882548826488274882848829488304883148832488334883448835488364883748838488394884048841488424884348844488454884648847488484884948850488514885248853488544885548856488574885848859488604886148862488634886448865488664886748868488694887048871488724887348874488754887648877488784887948880488814888248883488844888548886488874888848889488904889148892488934889448895488964889748898488994890048901489024890348904489054890648907489084890948910489114891248913489144891548916489174891848919489204892148922489234892448925489264892748928489294893048931489324893348934489354893648937489384893948940489414894248943489444894548946489474894848949489504895148952489534895448955489564895748958489594896048961489624896348964489654896648967489684896948970489714897248973489744897548976489774897848979489804898148982489834898448985489864898748988489894899048991489924899348994489954899648997489984899949000490014900249003490044900549006490074900849009490104901149012490134901449015490164901749018490194902049021490224902349024490254902649027490284902949030490314903249033490344903549036490374903849039490404904149042490434904449045490464904749048490494905049051490524905349054490554905649057490584905949060490614906249063490644906549066490674906849069490704907149072490734907449075490764907749078490794908049081490824908349084490854908649087490884908949090490914909249093490944909549096490974909849099491004910149102491034910449105491064910749108491094911049111491124911349114491154911649117491184911949120491214912249123491244912549126491274912849129491304913149132491334913449135491364913749138491394914049141491424914349144491454914649147491484914949150491514915249153491544915549156491574915849159491604916149162491634916449165491664916749168491694917049171491724917349174491754917649177491784917949180491814918249183491844918549186491874918849189491904919149192491934919449195491964919749198491994920049201492024920349204492054920649207492084920949210492114921249213492144921549216492174921849219492204922149222492234922449225492264922749228492294923049231492324923349234492354923649237492384923949240492414924249243492444924549246492474924849249492504925149252492534925449255492564925749258492594926049261492624926349264492654926649267492684926949270492714927249273492744927549276492774927849279492804928149282492834928449285492864928749288492894929049291492924929349294492954929649297492984929949300493014930249303493044930549306493074930849309493104931149312493134931449315493164931749318493194932049321493224932349324493254932649327493284932949330493314933249333493344933549336493374933849339493404934149342493434934449345493464934749348493494935049351493524935349354493554935649357493584935949360493614936249363493644936549366493674936849369493704937149372493734937449375493764937749378493794938049381493824938349384493854938649387493884938949390493914939249393493944939549396493974939849399494004940149402494034940449405494064940749408494094941049411494124941349414494154941649417494184941949420494214942249423494244942549426494274942849429494304943149432494334943449435494364943749438494394944049441494424944349444494454944649447494484944949450494514945249453494544945549456494574945849459494604946149462494634946449465494664946749468494694947049471494724947349474494754947649477494784947949480494814948249483494844948549486494874948849489494904949149492494934949449495494964949749498494994950049501495024950349504495054950649507495084950949510495114951249513495144951549516495174951849519495204952149522495234952449525495264952749528495294953049531495324953349534495354953649537495384953949540495414954249543495444954549546495474954849549495504955149552495534955449555495564955749558495594956049561495624956349564495654956649567495684956949570495714957249573495744957549576495774957849579495804958149582495834958449585495864958749588495894959049591495924959349594495954959649597495984959949600496014960249603496044960549606496074960849609496104961149612496134961449615496164961749618496194962049621496224962349624496254962649627496284962949630496314963249633496344963549636496374963849639496404964149642496434964449645496464964749648496494965049651496524965349654496554965649657496584965949660496614966249663496644966549666496674966849669496704967149672496734967449675496764967749678496794968049681496824968349684496854968649687496884968949690496914969249693496944969549696496974969849699497004970149702497034970449705497064970749708497094971049711497124971349714497154971649717497184971949720497214972249723497244972549726497274972849729497304973149732497334973449735497364973749738497394974049741497424974349744497454974649747497484974949750497514975249753497544975549756497574975849759497604976149762497634976449765497664976749768497694977049771497724977349774497754977649777497784977949780497814978249783497844978549786497874978849789497904979149792497934979449795497964979749798497994980049801498024980349804498054980649807498084980949810498114981249813498144981549816498174981849819498204982149822498234982449825498264982749828498294983049831498324983349834498354983649837498384983949840498414984249843498444984549846498474984849849498504985149852498534985449855498564985749858498594986049861498624986349864498654986649867498684986949870498714987249873498744987549876498774987849879498804988149882498834988449885498864988749888498894989049891498924989349894498954989649897498984989949900499014990249903499044990549906499074990849909499104991149912499134991449915499164991749918499194992049921499224992349924499254992649927499284992949930499314993249933499344993549936499374993849939499404994149942499434994449945499464994749948499494995049951499524995349954499554995649957499584995949960499614996249963499644996549966499674996849969499704997149972499734997449975499764997749978499794998049981499824998349984499854998649987499884998949990499914999249993499944999549996499974999849999500005000150002500035000450005500065000750008500095001050011500125001350014500155001650017500185001950020500215002250023500245002550026500275002850029500305003150032500335003450035500365003750038500395004050041500425004350044500455004650047500485004950050500515005250053500545005550056500575005850059500605006150062500635006450065500665006750068500695007050071500725007350074500755007650077500785007950080500815008250083500845008550086500875008850089500905009150092500935009450095500965009750098500995010050101501025010350104501055010650107501085010950110501115011250113501145011550116501175011850119501205012150122501235012450125501265012750128501295013050131501325013350134501355013650137501385013950140501415014250143501445014550146501475014850149501505015150152501535015450155501565015750158501595016050161501625016350164501655016650167501685016950170501715017250173501745017550176501775017850179501805018150182501835018450185501865018750188501895019050191501925019350194501955019650197501985019950200502015020250203502045020550206502075020850209502105021150212502135021450215502165021750218502195022050221502225022350224502255022650227502285022950230502315023250233502345023550236502375023850239502405024150242502435024450245502465024750248502495025050251502525025350254502555025650257502585025950260502615026250263502645026550266502675026850269502705027150272502735027450275502765027750278502795028050281502825028350284502855028650287502885028950290502915029250293502945029550296502975029850299503005030150302503035030450305503065030750308503095031050311503125031350314503155031650317503185031950320503215032250323503245032550326503275032850329503305033150332503335033450335503365033750338503395034050341503425034350344503455034650347503485034950350503515035250353503545035550356503575035850359503605036150362503635036450365503665036750368503695037050371503725037350374503755037650377503785037950380503815038250383503845038550386503875038850389503905039150392503935039450395503965039750398503995040050401504025040350404504055040650407504085040950410504115041250413504145041550416504175041850419504205042150422504235042450425504265042750428504295043050431504325043350434504355043650437504385043950440504415044250443504445044550446504475044850449504505045150452504535045450455504565045750458504595046050461504625046350464504655046650467504685046950470504715047250473504745047550476504775047850479504805048150482504835048450485504865048750488504895049050491504925049350494504955049650497504985049950500505015050250503505045050550506505075050850509505105051150512505135051450515505165051750518505195052050521505225052350524505255052650527505285052950530505315053250533505345053550536505375053850539505405054150542505435054450545505465054750548505495055050551505525055350554505555055650557505585055950560505615056250563505645056550566505675056850569505705057150572505735057450575505765057750578505795058050581505825058350584505855058650587505885058950590505915059250593505945059550596505975059850599506005060150602506035060450605506065060750608506095061050611506125061350614506155061650617506185061950620506215062250623506245062550626506275062850629506305063150632506335063450635506365063750638506395064050641506425064350644506455064650647506485064950650506515065250653506545065550656506575065850659506605066150662506635066450665506665066750668506695067050671506725067350674506755067650677506785067950680506815068250683506845068550686506875068850689506905069150692506935069450695506965069750698506995070050701507025070350704507055070650707507085070950710507115071250713507145071550716507175071850719507205072150722507235072450725507265072750728507295073050731507325073350734507355073650737507385073950740507415074250743507445074550746507475074850749507505075150752507535075450755507565075750758507595076050761507625076350764507655076650767507685076950770507715077250773507745077550776507775077850779507805078150782507835078450785507865078750788507895079050791507925079350794507955079650797507985079950800508015080250803508045080550806508075080850809508105081150812508135081450815508165081750818508195082050821508225082350824508255082650827508285082950830508315083250833508345083550836508375083850839508405084150842508435084450845508465084750848508495085050851508525085350854508555085650857508585085950860508615086250863508645086550866508675086850869508705087150872508735087450875508765087750878508795088050881508825088350884508855088650887508885088950890508915089250893508945089550896508975089850899509005090150902509035090450905509065090750908509095091050911509125091350914509155091650917509185091950920509215092250923509245092550926509275092850929509305093150932509335093450935509365093750938509395094050941509425094350944509455094650947509485094950950509515095250953509545095550956509575095850959509605096150962509635096450965509665096750968509695097050971509725097350974509755097650977509785097950980509815098250983509845098550986509875098850989509905099150992509935099450995509965099750998509995100051001510025100351004510055100651007510085100951010510115101251013510145101551016510175101851019510205102151022510235102451025510265102751028510295103051031510325103351034510355103651037510385103951040510415104251043510445104551046510475104851049510505105151052510535105451055510565105751058510595106051061510625106351064510655106651067510685106951070510715107251073510745107551076510775107851079510805108151082510835108451085510865108751088510895109051091510925109351094510955109651097510985109951100511015110251103511045110551106511075110851109511105111151112511135111451115511165111751118511195112051121511225112351124511255112651127511285112951130511315113251133511345113551136511375113851139511405114151142511435114451145511465114751148511495115051151511525115351154511555115651157511585115951160511615116251163511645116551166511675116851169511705117151172511735117451175511765117751178511795118051181511825118351184511855118651187511885118951190511915119251193511945119551196511975119851199512005120151202512035120451205512065120751208512095121051211512125121351214512155121651217512185121951220512215122251223512245122551226512275122851229512305123151232512335123451235512365123751238512395124051241512425124351244512455124651247512485124951250512515125251253512545125551256512575125851259512605126151262512635126451265512665126751268512695127051271512725127351274512755127651277512785127951280512815128251283512845128551286512875128851289512905129151292512935129451295512965129751298512995130051301513025130351304513055130651307513085130951310513115131251313513145131551316513175131851319513205132151322513235132451325513265132751328513295133051331513325133351334513355133651337513385133951340513415134251343513445134551346513475134851349513505135151352513535135451355513565135751358513595136051361513625136351364513655136651367513685136951370513715137251373513745137551376513775137851379513805138151382513835138451385513865138751388513895139051391513925139351394513955139651397513985139951400514015140251403514045140551406514075140851409514105141151412514135141451415514165141751418514195142051421514225142351424514255142651427514285142951430514315143251433514345143551436514375143851439514405144151442514435144451445514465144751448514495145051451514525145351454514555145651457514585145951460514615146251463514645146551466514675146851469514705147151472514735147451475514765147751478514795148051481514825148351484514855148651487514885148951490514915149251493514945149551496514975149851499515005150151502515035150451505515065150751508515095151051511515125151351514515155151651517515185151951520515215152251523515245152551526515275152851529515305153151532515335153451535515365153751538515395154051541515425154351544515455154651547515485154951550515515155251553515545155551556515575155851559515605156151562515635156451565515665156751568515695157051571515725157351574515755157651577515785157951580515815158251583515845158551586515875158851589515905159151592515935159451595515965159751598515995160051601516025160351604516055160651607516085160951610516115161251613516145161551616516175161851619516205162151622516235162451625516265162751628516295163051631516325163351634516355163651637516385163951640516415164251643516445164551646516475164851649516505165151652516535165451655516565165751658516595166051661516625166351664516655166651667516685166951670516715167251673516745167551676516775167851679516805168151682516835168451685516865168751688516895169051691516925169351694516955169651697516985169951700517015170251703517045170551706517075170851709517105171151712517135171451715517165171751718517195172051721517225172351724517255172651727517285172951730517315173251733517345173551736517375173851739517405174151742517435174451745517465174751748517495175051751517525175351754517555175651757517585175951760517615176251763517645176551766517675176851769517705177151772517735177451775517765177751778517795178051781517825178351784517855178651787517885178951790517915179251793517945179551796517975179851799518005180151802518035180451805518065180751808518095181051811518125181351814518155181651817518185181951820518215182251823518245182551826518275182851829518305183151832518335183451835518365183751838518395184051841518425184351844518455184651847518485184951850518515185251853518545185551856518575185851859518605186151862518635186451865518665186751868518695187051871518725187351874518755187651877518785187951880518815188251883518845188551886518875188851889518905189151892518935189451895518965189751898518995190051901519025190351904519055190651907519085190951910519115191251913519145191551916519175191851919519205192151922519235192451925519265192751928519295193051931519325193351934519355193651937519385193951940519415194251943519445194551946519475194851949519505195151952519535195451955519565195751958519595196051961519625196351964519655196651967519685196951970519715197251973519745197551976519775197851979519805198151982519835198451985519865198751988519895199051991519925199351994519955199651997519985199952000520015200252003520045200552006520075200852009520105201152012520135201452015520165201752018520195202052021520225202352024520255202652027520285202952030520315203252033520345203552036520375203852039520405204152042520435204452045520465204752048520495205052051520525205352054520555205652057520585205952060520615206252063520645206552066520675206852069520705207152072520735207452075520765207752078520795208052081520825208352084520855208652087520885208952090520915209252093520945209552096520975209852099521005210152102521035210452105521065210752108521095211052111521125211352114521155211652117521185211952120521215212252123521245212552126521275212852129521305213152132521335213452135521365213752138521395214052141521425214352144521455214652147521485214952150521515215252153521545215552156521575215852159521605216152162521635216452165521665216752168521695217052171521725217352174521755217652177521785217952180521815218252183521845218552186521875218852189521905219152192521935219452195521965219752198521995220052201522025220352204522055220652207522085220952210522115221252213522145221552216522175221852219522205222152222522235222452225522265222752228522295223052231522325223352234522355223652237522385223952240522415224252243522445224552246522475224852249522505225152252522535225452255522565225752258522595226052261522625226352264522655226652267522685226952270522715227252273522745227552276522775227852279522805228152282522835228452285522865228752288522895229052291522925229352294522955229652297522985229952300523015230252303523045230552306523075230852309523105231152312523135231452315523165231752318523195232052321523225232352324523255232652327523285232952330523315233252333523345233552336523375233852339523405234152342523435234452345523465234752348523495235052351523525235352354523555235652357523585235952360523615236252363523645236552366523675236852369523705237152372523735237452375523765237752378523795238052381523825238352384523855238652387523885238952390523915239252393523945239552396523975239852399524005240152402524035240452405524065240752408524095241052411524125241352414524155241652417524185241952420524215242252423524245242552426524275242852429524305243152432524335243452435524365243752438524395244052441524425244352444524455244652447524485244952450524515245252453524545245552456524575245852459524605246152462524635246452465524665246752468524695247052471524725247352474524755247652477524785247952480524815248252483524845248552486524875248852489524905249152492524935249452495524965249752498524995250052501525025250352504525055250652507525085250952510525115251252513525145251552516525175251852519525205252152522525235252452525525265252752528525295253052531525325253352534525355253652537525385253952540525415254252543525445254552546525475254852549525505255152552525535255452555525565255752558525595256052561525625256352564525655256652567525685256952570525715257252573525745257552576525775257852579525805258152582525835258452585525865258752588525895259052591525925259352594525955259652597525985259952600526015260252603526045260552606526075260852609526105261152612526135261452615526165261752618526195262052621526225262352624526255262652627526285262952630526315263252633526345263552636526375263852639526405264152642526435264452645526465264752648526495265052651526525265352654526555265652657526585265952660526615266252663526645266552666526675266852669526705267152672526735267452675526765267752678526795268052681526825268352684526855268652687526885268952690526915269252693526945269552696526975269852699527005270152702527035270452705527065270752708527095271052711527125271352714527155271652717527185271952720527215272252723527245272552726527275272852729527305273152732527335273452735527365273752738527395274052741527425274352744527455274652747527485274952750527515275252753527545275552756527575275852759527605276152762527635276452765527665276752768527695277052771527725277352774527755277652777527785277952780527815278252783527845278552786527875278852789527905279152792527935279452795527965279752798527995280052801528025280352804528055280652807528085280952810528115281252813528145281552816528175281852819528205282152822528235282452825528265282752828528295283052831528325283352834528355283652837528385283952840528415284252843528445284552846528475284852849528505285152852528535285452855528565285752858528595286052861528625286352864528655286652867528685286952870528715287252873528745287552876528775287852879528805288152882528835288452885528865288752888528895289052891528925289352894528955289652897528985289952900529015290252903529045290552906529075290852909529105291152912529135291452915529165291752918529195292052921529225292352924529255292652927529285292952930529315293252933529345293552936529375293852939529405294152942529435294452945529465294752948529495295052951529525295352954529555295652957529585295952960529615296252963529645296552966529675296852969529705297152972529735297452975529765297752978529795298052981529825298352984529855298652987529885298952990529915299252993529945299552996529975299852999530005300153002530035300453005530065300753008530095301053011530125301353014530155301653017530185301953020530215302253023530245302553026530275302853029530305303153032530335303453035530365303753038530395304053041530425304353044530455304653047530485304953050530515305253053530545305553056530575305853059530605306153062530635306453065530665306753068530695307053071530725307353074530755307653077530785307953080530815308253083530845308553086530875308853089530905309153092530935309453095530965309753098530995310053101531025310353104531055310653107531085310953110531115311253113531145311553116531175311853119531205312153122531235312453125531265312753128531295313053131531325313353134531355313653137531385313953140531415314253143531445314553146531475314853149531505315153152531535315453155531565315753158531595316053161531625316353164531655316653167531685316953170531715317253173531745317553176531775317853179531805318153182531835318453185531865318753188531895319053191531925319353194531955319653197531985319953200532015320253203532045320553206532075320853209532105321153212532135321453215532165321753218532195322053221532225322353224532255322653227532285322953230532315323253233532345323553236532375323853239532405324153242532435324453245532465324753248532495325053251532525325353254532555325653257532585325953260532615326253263532645326553266532675326853269532705327153272532735327453275532765327753278532795328053281532825328353284532855328653287532885328953290532915329253293532945329553296532975329853299533005330153302533035330453305533065330753308533095331053311533125331353314533155331653317533185331953320533215332253323533245332553326533275332853329533305333153332533335333453335533365333753338533395334053341533425334353344533455334653347533485334953350533515335253353533545335553356533575335853359533605336153362533635336453365533665336753368533695337053371533725337353374533755337653377533785337953380533815338253383533845338553386533875338853389533905339153392533935339453395533965339753398533995340053401534025340353404534055340653407534085340953410534115341253413534145341553416534175341853419534205342153422534235342453425534265342753428534295343053431534325343353434534355343653437534385343953440534415344253443534445344553446534475344853449534505345153452534535345453455534565345753458534595346053461534625346353464534655346653467534685346953470534715347253473534745347553476534775347853479534805348153482534835348453485534865348753488534895349053491534925349353494534955349653497534985349953500535015350253503535045350553506535075350853509535105351153512535135351453515535165351753518535195352053521535225352353524535255352653527535285352953530535315353253533535345353553536535375353853539535405354153542535435354453545535465354753548535495355053551535525355353554535555355653557535585355953560535615356253563535645356553566535675356853569535705357153572535735357453575535765357753578535795358053581535825358353584535855358653587535885358953590535915359253593535945359553596535975359853599536005360153602536035360453605536065360753608536095361053611536125361353614536155361653617536185361953620536215362253623536245362553626536275362853629536305363153632536335363453635536365363753638536395364053641536425364353644536455364653647536485364953650536515365253653536545365553656536575365853659536605366153662536635366453665536665366753668536695367053671536725367353674536755367653677536785367953680536815368253683536845368553686536875368853689536905369153692536935369453695536965369753698536995370053701537025370353704537055370653707537085370953710537115371253713537145371553716537175371853719537205372153722537235372453725537265372753728537295373053731537325373353734537355373653737537385373953740537415374253743537445374553746537475374853749537505375153752537535375453755537565375753758537595376053761537625376353764537655376653767537685376953770537715377253773537745377553776537775377853779537805378153782537835378453785537865378753788537895379053791537925379353794537955379653797537985379953800538015380253803538045380553806538075380853809538105381153812538135381453815538165381753818538195382053821538225382353824538255382653827538285382953830538315383253833538345383553836538375383853839538405384153842538435384453845538465384753848538495385053851538525385353854538555385653857538585385953860538615386253863538645386553866538675386853869538705387153872538735387453875538765387753878538795388053881538825388353884538855388653887538885388953890538915389253893538945389553896538975389853899539005390153902539035390453905539065390753908539095391053911539125391353914539155391653917539185391953920539215392253923539245392553926539275392853929539305393153932539335393453935539365393753938539395394053941539425394353944539455394653947539485394953950539515395253953539545395553956539575395853959539605396153962539635396453965539665396753968539695397053971539725397353974539755397653977539785397953980539815398253983539845398553986539875398853989539905399153992539935399453995539965399753998539995400054001540025400354004540055400654007540085400954010540115401254013540145401554016540175401854019540205402154022540235402454025540265402754028540295403054031540325403354034540355403654037540385403954040540415404254043540445404554046540475404854049540505405154052540535405454055540565405754058540595406054061540625406354064540655406654067540685406954070540715407254073540745407554076540775407854079540805408154082540835408454085540865408754088540895409054091540925409354094540955409654097540985409954100541015410254103541045410554106541075410854109541105411154112541135411454115541165411754118541195412054121541225412354124541255412654127541285412954130541315413254133541345413554136541375413854139541405414154142541435414454145541465414754148541495415054151541525415354154541555415654157541585415954160541615416254163541645416554166541675416854169541705417154172541735417454175541765417754178541795418054181541825418354184541855418654187541885418954190541915419254193541945419554196541975419854199542005420154202542035420454205542065420754208542095421054211542125421354214542155421654217542185421954220542215422254223542245422554226542275422854229542305423154232542335423454235542365423754238542395424054241542425424354244542455424654247542485424954250542515425254253542545425554256542575425854259542605426154262542635426454265542665426754268542695427054271542725427354274542755427654277542785427954280542815428254283542845428554286542875428854289542905429154292542935429454295542965429754298542995430054301543025430354304543055430654307543085430954310543115431254313543145431554316543175431854319543205432154322543235432454325543265432754328543295433054331543325433354334543355433654337543385433954340543415434254343543445434554346543475434854349543505435154352543535435454355543565435754358543595436054361543625436354364543655436654367543685436954370543715437254373543745437554376543775437854379543805438154382543835438454385543865438754388543895439054391543925439354394543955439654397543985439954400544015440254403544045440554406544075440854409544105441154412544135441454415544165441754418544195442054421544225442354424544255442654427544285442954430544315443254433544345443554436544375443854439544405444154442544435444454445544465444754448544495445054451544525445354454544555445654457544585445954460544615446254463544645446554466544675446854469544705447154472544735447454475544765447754478544795448054481544825448354484544855448654487544885448954490544915449254493544945449554496544975449854499545005450154502545035450454505545065450754508545095451054511545125451354514545155451654517545185451954520545215452254523545245452554526545275452854529545305453154532545335453454535545365453754538545395454054541545425454354544545455454654547545485454954550545515455254553545545455554556545575455854559545605456154562545635456454565545665456754568545695457054571545725457354574545755457654577545785457954580545815458254583545845458554586545875458854589545905459154592545935459454595545965459754598545995460054601546025460354604546055460654607546085460954610546115461254613546145461554616546175461854619546205462154622546235462454625546265462754628546295463054631546325463354634546355463654637546385463954640546415464254643546445464554646546475464854649546505465154652546535465454655546565465754658546595466054661546625466354664546655466654667546685466954670546715467254673546745467554676546775467854679546805468154682546835468454685546865468754688546895469054691546925469354694546955469654697546985469954700547015470254703547045470554706547075470854709547105471154712547135471454715547165471754718547195472054721547225472354724547255472654727547285472954730547315473254733547345473554736547375473854739547405474154742547435474454745547465474754748547495475054751547525475354754547555475654757547585475954760547615476254763547645476554766547675476854769547705477154772547735477454775547765477754778547795478054781547825478354784547855478654787547885478954790547915479254793547945479554796547975479854799548005480154802548035480454805548065480754808548095481054811548125481354814548155481654817548185481954820548215482254823548245482554826548275482854829548305483154832548335483454835548365483754838548395484054841548425484354844548455484654847548485484954850548515485254853548545485554856548575485854859548605486154862548635486454865548665486754868548695487054871548725487354874548755487654877548785487954880548815488254883548845488554886548875488854889548905489154892548935489454895548965489754898548995490054901549025490354904549055490654907549085490954910549115491254913549145491554916549175491854919549205492154922549235492454925549265492754928549295493054931549325493354934549355493654937549385493954940549415494254943549445494554946549475494854949549505495154952549535495454955549565495754958549595496054961549625496354964549655496654967549685496954970549715497254973549745497554976549775497854979549805498154982549835498454985549865498754988549895499054991549925499354994549955499654997549985499955000550015500255003550045500555006550075500855009550105501155012550135501455015550165501755018550195502055021550225502355024550255502655027550285502955030550315503255033550345503555036550375503855039550405504155042550435504455045550465504755048550495505055051550525505355054550555505655057550585505955060550615506255063550645506555066550675506855069550705507155072550735507455075550765507755078550795508055081550825508355084550855508655087550885508955090550915509255093550945509555096550975509855099551005510155102551035510455105551065510755108551095511055111551125511355114551155511655117551185511955120551215512255123551245512555126551275512855129551305513155132551335513455135551365513755138551395514055141551425514355144551455514655147551485514955150551515515255153551545515555156551575515855159551605516155162551635516455165551665516755168551695517055171551725517355174551755517655177551785517955180551815518255183551845518555186551875518855189551905519155192551935519455195551965519755198551995520055201552025520355204552055520655207552085520955210552115521255213552145521555216552175521855219552205522155222552235522455225552265522755228552295523055231552325523355234552355523655237552385523955240552415524255243552445524555246552475524855249552505525155252552535525455255552565525755258552595526055261552625526355264552655526655267552685526955270552715527255273552745527555276552775527855279552805528155282552835528455285552865528755288552895529055291552925529355294552955529655297552985529955300553015530255303553045530555306553075530855309553105531155312553135531455315553165531755318553195532055321553225532355324553255532655327553285532955330553315533255333553345533555336553375533855339553405534155342553435534455345553465534755348553495535055351553525535355354553555535655357553585535955360553615536255363553645536555366553675536855369553705537155372553735537455375553765537755378553795538055381553825538355384553855538655387553885538955390553915539255393553945539555396553975539855399554005540155402554035540455405554065540755408554095541055411554125541355414554155541655417554185541955420554215542255423554245542555426554275542855429554305543155432554335543455435554365543755438554395544055441554425544355444554455544655447554485544955450554515545255453554545545555456554575545855459554605546155462554635546455465554665546755468554695547055471554725547355474554755547655477554785547955480554815548255483554845548555486554875548855489554905549155492554935549455495554965549755498554995550055501555025550355504555055550655507555085550955510555115551255513555145551555516555175551855519555205552155522555235552455525555265552755528555295553055531555325553355534555355553655537555385553955540555415554255543555445554555546555475554855549555505555155552555535555455555555565555755558555595556055561555625556355564555655556655567555685556955570555715557255573555745557555576555775557855579555805558155582555835558455585555865558755588555895559055591555925559355594555955559655597555985559955600556015560255603556045560555606556075560855609556105561155612556135561455615556165561755618556195562055621556225562355624556255562655627556285562955630556315563255633556345563555636556375563855639556405564155642556435564455645556465564755648556495565055651556525565355654556555565655657556585565955660556615566255663556645566555666556675566855669556705567155672556735567455675556765567755678556795568055681556825568355684556855568655687556885568955690556915569255693556945569555696556975569855699557005570155702557035570455705557065570755708557095571055711557125571355714557155571655717557185571955720557215572255723557245572555726557275572855729557305573155732557335573455735557365573755738557395574055741557425574355744557455574655747557485574955750557515575255753557545575555756557575575855759557605576155762557635576455765557665576755768557695577055771557725577355774557755577655777557785577955780557815578255783557845578555786557875578855789557905579155792557935579455795557965579755798557995580055801558025580355804558055580655807558085580955810558115581255813558145581555816558175581855819558205582155822558235582455825558265582755828558295583055831558325583355834558355583655837558385583955840558415584255843558445584555846558475584855849558505585155852558535585455855558565585755858558595586055861558625586355864558655586655867558685586955870558715587255873558745587555876558775587855879558805588155882558835588455885558865588755888558895589055891558925589355894558955589655897558985589955900559015590255903559045590555906559075590855909559105591155912559135591455915559165591755918559195592055921559225592355924559255592655927559285592955930559315593255933559345593555936559375593855939559405594155942559435594455945559465594755948559495595055951559525595355954559555595655957559585595955960559615596255963559645596555966559675596855969559705597155972559735597455975559765597755978559795598055981559825598355984559855598655987559885598955990559915599255993559945599555996559975599855999560005600156002560035600456005560065600756008560095601056011560125601356014560155601656017560185601956020560215602256023560245602556026560275602856029560305603156032560335603456035560365603756038560395604056041560425604356044560455604656047560485604956050560515605256053560545605556056560575605856059560605606156062560635606456065560665606756068560695607056071560725607356074560755607656077560785607956080560815608256083560845608556086560875608856089560905609156092560935609456095560965609756098560995610056101561025610356104561055610656107561085610956110561115611256113561145611556116561175611856119561205612156122561235612456125561265612756128561295613056131561325613356134561355613656137561385613956140561415614256143561445614556146561475614856149561505615156152561535615456155561565615756158561595616056161561625616356164561655616656167561685616956170561715617256173561745617556176561775617856179561805618156182561835618456185561865618756188561895619056191561925619356194561955619656197561985619956200562015620256203562045620556206562075620856209562105621156212562135621456215562165621756218562195622056221562225622356224562255622656227562285622956230562315623256233562345623556236562375623856239562405624156242562435624456245562465624756248562495625056251562525625356254562555625656257562585625956260562615626256263562645626556266562675626856269562705627156272562735627456275562765627756278562795628056281562825628356284562855628656287562885628956290562915629256293562945629556296562975629856299563005630156302563035630456305563065630756308563095631056311563125631356314563155631656317563185631956320563215632256323563245632556326563275632856329563305633156332563335633456335563365633756338563395634056341563425634356344563455634656347563485634956350563515635256353563545635556356563575635856359563605636156362563635636456365563665636756368563695637056371563725637356374563755637656377563785637956380563815638256383563845638556386563875638856389563905639156392563935639456395563965639756398563995640056401564025640356404564055640656407564085640956410564115641256413564145641556416564175641856419564205642156422564235642456425564265642756428564295643056431564325643356434564355643656437564385643956440564415644256443564445644556446564475644856449564505645156452564535645456455564565645756458564595646056461564625646356464564655646656467564685646956470564715647256473564745647556476564775647856479564805648156482564835648456485564865648756488564895649056491564925649356494564955649656497564985649956500565015650256503565045650556506565075650856509565105651156512565135651456515565165651756518565195652056521565225652356524565255652656527565285652956530565315653256533565345653556536565375653856539565405654156542565435654456545565465654756548565495655056551565525655356554565555655656557565585655956560565615656256563565645656556566565675656856569565705657156572565735657456575565765657756578565795658056581565825658356584565855658656587565885658956590565915659256593565945659556596565975659856599566005660156602566035660456605566065660756608566095661056611566125661356614566155661656617566185661956620566215662256623566245662556626566275662856629566305663156632566335663456635566365663756638566395664056641566425664356644566455664656647566485664956650566515665256653566545665556656566575665856659566605666156662566635666456665566665666756668566695667056671566725667356674566755667656677566785667956680566815668256683566845668556686566875668856689566905669156692566935669456695566965669756698566995670056701567025670356704567055670656707567085670956710567115671256713567145671556716567175671856719567205672156722567235672456725567265672756728567295673056731567325673356734567355673656737567385673956740567415674256743567445674556746567475674856749567505675156752567535675456755567565675756758567595676056761567625676356764567655676656767567685676956770567715677256773567745677556776567775677856779567805678156782567835678456785567865678756788567895679056791567925679356794567955679656797567985679956800568015680256803568045680556806568075680856809568105681156812568135681456815568165681756818568195682056821568225682356824568255682656827568285682956830568315683256833568345683556836568375683856839568405684156842568435684456845568465684756848568495685056851568525685356854568555685656857568585685956860568615686256863568645686556866568675686856869568705687156872568735687456875568765687756878568795688056881568825688356884568855688656887568885688956890568915689256893568945689556896568975689856899569005690156902569035690456905569065690756908569095691056911569125691356914569155691656917569185691956920569215692256923569245692556926569275692856929569305693156932569335693456935569365693756938569395694056941569425694356944569455694656947569485694956950569515695256953569545695556956569575695856959569605696156962569635696456965569665696756968569695697056971569725697356974569755697656977569785697956980569815698256983569845698556986569875698856989569905699156992569935699456995569965699756998569995700057001570025700357004570055700657007570085700957010570115701257013570145701557016570175701857019570205702157022570235702457025570265702757028570295703057031570325703357034570355703657037570385703957040570415704257043570445704557046570475704857049570505705157052570535705457055570565705757058570595706057061570625706357064570655706657067570685706957070570715707257073570745707557076570775707857079570805708157082570835708457085570865708757088570895709057091570925709357094570955709657097570985709957100571015710257103571045710557106571075710857109571105711157112571135711457115571165711757118571195712057121571225712357124571255712657127571285712957130571315713257133571345713557136571375713857139571405714157142571435714457145571465714757148571495715057151571525715357154571555715657157571585715957160571615716257163571645716557166571675716857169571705717157172571735717457175571765717757178571795718057181571825718357184571855718657187571885718957190571915719257193571945719557196571975719857199572005720157202572035720457205572065720757208572095721057211572125721357214572155721657217572185721957220572215722257223572245722557226572275722857229572305723157232572335723457235572365723757238572395724057241572425724357244572455724657247572485724957250572515725257253572545725557256572575725857259572605726157262572635726457265572665726757268572695727057271572725727357274572755727657277572785727957280572815728257283572845728557286572875728857289572905729157292572935729457295572965729757298572995730057301573025730357304573055730657307573085730957310573115731257313573145731557316573175731857319573205732157322573235732457325573265732757328573295733057331573325733357334573355733657337573385733957340573415734257343573445734557346573475734857349573505735157352573535735457355573565735757358573595736057361573625736357364573655736657367573685736957370573715737257373573745737557376573775737857379573805738157382573835738457385573865738757388573895739057391573925739357394573955739657397573985739957400574015740257403574045740557406574075740857409574105741157412574135741457415574165741757418574195742057421574225742357424574255742657427574285742957430574315743257433574345743557436574375743857439574405744157442574435744457445574465744757448574495745057451574525745357454574555745657457574585745957460574615746257463574645746557466574675746857469574705747157472574735747457475574765747757478574795748057481574825748357484574855748657487574885748957490574915749257493574945749557496574975749857499575005750157502575035750457505575065750757508575095751057511575125751357514575155751657517575185751957520575215752257523575245752557526575275752857529575305753157532575335753457535575365753757538575395754057541575425754357544575455754657547575485754957550575515755257553575545755557556575575755857559575605756157562575635756457565575665756757568575695757057571575725757357574575755757657577575785757957580575815758257583575845758557586575875758857589575905759157592575935759457595575965759757598575995760057601576025760357604576055760657607576085760957610576115761257613576145761557616576175761857619576205762157622576235762457625576265762757628576295763057631576325763357634576355763657637576385763957640576415764257643576445764557646576475764857649576505765157652576535765457655576565765757658576595766057661576625766357664576655766657667576685766957670576715767257673576745767557676576775767857679576805768157682576835768457685576865768757688576895769057691576925769357694576955769657697576985769957700577015770257703577045770557706577075770857709577105771157712577135771457715577165771757718577195772057721577225772357724577255772657727577285772957730577315773257733577345773557736577375773857739577405774157742577435774457745577465774757748577495775057751577525775357754577555775657757577585775957760577615776257763577645776557766577675776857769577705777157772577735777457775577765777757778577795778057781577825778357784577855778657787577885778957790577915779257793577945779557796577975779857799578005780157802578035780457805578065780757808578095781057811578125781357814578155781657817578185781957820578215782257823578245782557826578275782857829578305783157832578335783457835578365783757838578395784057841578425784357844578455784657847578485784957850578515785257853578545785557856578575785857859578605786157862578635786457865578665786757868578695787057871578725787357874578755787657877578785787957880578815788257883578845788557886578875788857889578905789157892578935789457895578965789757898578995790057901579025790357904579055790657907579085790957910579115791257913579145791557916579175791857919579205792157922579235792457925579265792757928579295793057931579325793357934579355793657937579385793957940579415794257943579445794557946579475794857949579505795157952579535795457955579565795757958579595796057961579625796357964579655796657967579685796957970579715797257973579745797557976579775797857979579805798157982579835798457985579865798757988579895799057991579925799357994579955799657997579985799958000580015800258003580045800558006580075800858009580105801158012580135801458015580165801758018580195802058021580225802358024580255802658027580285802958030580315803258033580345803558036580375803858039580405804158042580435804458045580465804758048580495805058051580525805358054580555805658057580585805958060580615806258063580645806558066580675806858069580705807158072580735807458075580765807758078580795808058081580825808358084580855808658087580885808958090580915809258093580945809558096580975809858099581005810158102581035810458105581065810758108581095811058111581125811358114581155811658117581185811958120581215812258123581245812558126581275812858129581305813158132581335813458135581365813758138581395814058141581425814358144581455814658147581485814958150581515815258153581545815558156581575815858159581605816158162581635816458165581665816758168581695817058171581725817358174581755817658177581785817958180581815818258183581845818558186581875818858189581905819158192581935819458195581965819758198581995820058201582025820358204582055820658207582085820958210582115821258213582145821558216582175821858219582205822158222582235822458225582265822758228582295823058231582325823358234582355823658237582385823958240582415824258243582445824558246582475824858249582505825158252582535825458255582565825758258582595826058261582625826358264582655826658267582685826958270582715827258273582745827558276582775827858279582805828158282582835828458285582865828758288582895829058291582925829358294582955829658297582985829958300583015830258303583045830558306583075830858309583105831158312583135831458315583165831758318583195832058321583225832358324583255832658327583285832958330583315833258333583345833558336583375833858339583405834158342583435834458345583465834758348583495835058351583525835358354583555835658357583585835958360583615836258363583645836558366583675836858369583705837158372583735837458375583765837758378583795838058381583825838358384583855838658387583885838958390583915839258393583945839558396583975839858399584005840158402584035840458405584065840758408584095841058411584125841358414584155841658417584185841958420584215842258423584245842558426584275842858429584305843158432584335843458435584365843758438584395844058441584425844358444584455844658447584485844958450584515845258453584545845558456584575845858459584605846158462584635846458465584665846758468584695847058471584725847358474584755847658477584785847958480584815848258483584845848558486584875848858489584905849158492584935849458495584965849758498584995850058501585025850358504585055850658507585085850958510585115851258513585145851558516585175851858519585205852158522585235852458525585265852758528585295853058531585325853358534585355853658537585385853958540585415854258543585445854558546585475854858549585505855158552585535855458555585565855758558585595856058561585625856358564585655856658567585685856958570585715857258573585745857558576585775857858579585805858158582585835858458585585865858758588585895859058591585925859358594585955859658597585985859958600586015860258603586045860558606586075860858609586105861158612586135861458615586165861758618586195862058621586225862358624586255862658627586285862958630586315863258633586345863558636586375863858639586405864158642586435864458645586465864758648586495865058651586525865358654586555865658657586585865958660586615866258663586645866558666586675866858669586705867158672586735867458675586765867758678586795868058681586825868358684586855868658687586885868958690586915869258693586945869558696586975869858699587005870158702587035870458705587065870758708587095871058711587125871358714587155871658717587185871958720587215872258723587245872558726587275872858729587305873158732587335873458735587365873758738587395874058741587425874358744587455874658747587485874958750587515875258753587545875558756587575875858759587605876158762587635876458765587665876758768587695877058771587725877358774587755877658777587785877958780587815878258783587845878558786587875878858789587905879158792587935879458795587965879758798587995880058801588025880358804588055880658807588085880958810588115881258813588145881558816588175881858819588205882158822588235882458825588265882758828588295883058831588325883358834588355883658837588385883958840588415884258843588445884558846588475884858849588505885158852588535885458855588565885758858588595886058861588625886358864588655886658867588685886958870588715887258873588745887558876588775887858879588805888158882588835888458885588865888758888588895889058891588925889358894588955889658897588985889958900589015890258903589045890558906589075890858909589105891158912589135891458915589165891758918589195892058921589225892358924589255892658927589285892958930589315893258933589345893558936589375893858939589405894158942589435894458945589465894758948589495895058951589525895358954589555895658957589585895958960589615896258963589645896558966589675896858969589705897158972589735897458975589765897758978589795898058981589825898358984589855898658987589885898958990589915899258993589945899558996589975899858999590005900159002590035900459005590065900759008590095901059011590125901359014590155901659017590185901959020590215902259023590245902559026590275902859029590305903159032590335903459035590365903759038590395904059041590425904359044590455904659047590485904959050590515905259053590545905559056590575905859059590605906159062590635906459065590665906759068590695907059071590725907359074590755907659077590785907959080590815908259083590845908559086590875908859089590905909159092590935909459095590965909759098590995910059101591025910359104591055910659107591085910959110591115911259113591145911559116591175911859119591205912159122591235912459125591265912759128591295913059131591325913359134591355913659137591385913959140591415914259143591445914559146591475914859149591505915159152591535915459155591565915759158591595916059161591625916359164591655916659167591685916959170591715917259173591745917559176591775917859179591805918159182591835918459185591865918759188591895919059191591925919359194591955919659197591985919959200592015920259203592045920559206592075920859209592105921159212592135921459215592165921759218592195922059221592225922359224592255922659227592285922959230592315923259233592345923559236592375923859239592405924159242592435924459245592465924759248592495925059251592525925359254592555925659257592585925959260592615926259263592645926559266592675926859269592705927159272592735927459275592765927759278592795928059281592825928359284592855928659287592885928959290592915929259293592945929559296592975929859299593005930159302593035930459305593065930759308593095931059311593125931359314593155931659317593185931959320593215932259323593245932559326593275932859329593305933159332593335933459335593365933759338593395934059341593425934359344593455934659347593485934959350593515935259353593545935559356593575935859359593605936159362593635936459365593665936759368593695937059371593725937359374593755937659377593785937959380593815938259383593845938559386593875938859389593905939159392593935939459395593965939759398593995940059401594025940359404594055940659407594085940959410594115941259413594145941559416594175941859419594205942159422594235942459425594265942759428594295943059431594325943359434594355943659437594385943959440594415944259443594445944559446594475944859449594505945159452594535945459455594565945759458594595946059461594625946359464594655946659467594685946959470594715947259473594745947559476594775947859479594805948159482594835948459485594865948759488594895949059491594925949359494594955949659497594985949959500595015950259503595045950559506595075950859509595105951159512595135951459515595165951759518595195952059521595225952359524595255952659527595285952959530595315953259533595345953559536595375953859539595405954159542595435954459545595465954759548595495955059551595525955359554595555955659557595585955959560595615956259563595645956559566595675956859569595705957159572595735957459575595765957759578595795958059581595825958359584595855958659587595885958959590595915959259593595945959559596595975959859599596005960159602596035960459605596065960759608596095961059611596125961359614596155961659617596185961959620596215962259623596245962559626596275962859629596305963159632596335963459635596365963759638596395964059641596425964359644596455964659647596485964959650596515965259653596545965559656596575965859659596605966159662596635966459665596665966759668596695967059671596725967359674596755967659677596785967959680596815968259683596845968559686596875968859689596905969159692596935969459695596965969759698596995970059701597025970359704597055970659707597085970959710597115971259713597145971559716597175971859719597205972159722597235972459725597265972759728597295973059731597325973359734597355973659737597385973959740597415974259743597445974559746597475974859749597505975159752597535975459755597565975759758597595976059761597625976359764597655976659767597685976959770597715977259773597745977559776597775977859779597805978159782597835978459785597865978759788597895979059791597925979359794597955979659797597985979959800598015980259803598045980559806598075980859809598105981159812598135981459815598165981759818598195982059821598225982359824598255982659827598285982959830598315983259833598345983559836598375983859839598405984159842598435984459845598465984759848598495985059851598525985359854598555985659857598585985959860598615986259863598645986559866598675986859869598705987159872598735987459875598765987759878598795988059881598825988359884598855988659887598885988959890598915989259893598945989559896598975989859899599005990159902599035990459905599065990759908599095991059911599125991359914599155991659917599185991959920599215992259923599245992559926599275992859929599305993159932599335993459935599365993759938599395994059941599425994359944599455994659947599485994959950599515995259953599545995559956599575995859959599605996159962599635996459965599665996759968599695997059971599725997359974599755997659977599785997959980599815998259983599845998559986599875998859989599905999159992599935999459995599965999759998599996000060001600026000360004600056000660007600086000960010600116001260013600146001560016600176001860019600206002160022600236002460025600266002760028600296003060031600326003360034600356003660037600386003960040600416004260043600446004560046600476004860049600506005160052600536005460055600566005760058600596006060061600626006360064600656006660067600686006960070600716007260073600746007560076600776007860079600806008160082600836008460085600866008760088600896009060091600926009360094600956009660097600986009960100601016010260103601046010560106601076010860109601106011160112601136011460115601166011760118601196012060121601226012360124601256012660127601286012960130601316013260133601346013560136601376013860139601406014160142601436014460145601466014760148601496015060151601526015360154601556015660157601586015960160601616016260163601646016560166601676016860169601706017160172601736017460175601766017760178601796018060181601826018360184601856018660187601886018960190601916019260193601946019560196601976019860199602006020160202602036020460205602066020760208602096021060211602126021360214602156021660217602186021960220602216022260223602246022560226602276022860229602306023160232602336023460235602366023760238602396024060241602426024360244602456024660247602486024960250602516025260253602546025560256602576025860259602606026160262602636026460265602666026760268602696027060271602726027360274602756027660277602786027960280602816028260283602846028560286602876028860289602906029160292602936029460295602966029760298602996030060301603026030360304603056030660307603086030960310603116031260313603146031560316603176031860319603206032160322603236032460325603266032760328603296033060331603326033360334603356033660337603386033960340603416034260343603446034560346603476034860349603506035160352603536035460355603566035760358603596036060361603626036360364603656036660367603686036960370603716037260373603746037560376603776037860379603806038160382603836038460385603866038760388603896039060391603926039360394603956039660397603986039960400604016040260403604046040560406604076040860409604106041160412604136041460415604166041760418604196042060421604226042360424604256042660427604286042960430604316043260433604346043560436604376043860439604406044160442604436044460445604466044760448604496045060451604526045360454604556045660457604586045960460604616046260463604646046560466604676046860469604706047160472604736047460475604766047760478604796048060481604826048360484604856048660487604886048960490604916049260493604946049560496604976049860499605006050160502605036050460505605066050760508605096051060511605126051360514605156051660517605186051960520605216052260523605246052560526605276052860529605306053160532605336053460535605366053760538605396054060541605426054360544605456054660547605486054960550605516055260553605546055560556605576055860559605606056160562605636056460565605666056760568605696057060571605726057360574605756057660577605786057960580605816058260583605846058560586605876058860589605906059160592605936059460595605966059760598605996060060601606026060360604606056060660607606086060960610606116061260613606146061560616606176061860619606206062160622606236062460625606266062760628606296063060631606326063360634606356063660637606386063960640606416064260643606446064560646606476064860649606506065160652606536065460655606566065760658606596066060661606626066360664606656066660667606686066960670606716067260673606746067560676606776067860679606806068160682606836068460685606866068760688606896069060691606926069360694606956069660697606986069960700607016070260703607046070560706607076070860709607106071160712607136071460715607166071760718607196072060721607226072360724607256072660727607286072960730607316073260733607346073560736607376073860739607406074160742607436074460745607466074760748607496075060751607526075360754607556075660757607586075960760607616076260763607646076560766607676076860769607706077160772607736077460775607766077760778607796078060781607826078360784607856078660787607886078960790607916079260793607946079560796607976079860799608006080160802608036080460805608066080760808608096081060811608126081360814608156081660817608186081960820608216082260823608246082560826608276082860829608306083160832608336083460835608366083760838608396084060841608426084360844608456084660847
  1. /**
  2. * @license
  3. * Video.js 7.6.0 <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.6.0";
  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. } // `mouseup` event on a single left click has
  1187. // `button` and `buttons` equal to 0
  1188. if (event.button === 0 && event.buttons === 0) {
  1189. return true;
  1190. }
  1191. if (event.button !== 0 || event.buttons !== 1) {
  1192. // This is the reason we have those if else block above
  1193. // if any special case we can catch and let it slide
  1194. // we do it above, when get to here, this definitely
  1195. // is-not-left-click
  1196. return false;
  1197. }
  1198. return true;
  1199. }
  1200. /**
  1201. * Finds a single DOM element matching `selector` within the optional
  1202. * `context` of another DOM element (defaulting to `document`).
  1203. *
  1204. * @param {string} selector
  1205. * A valid CSS selector, which will be passed to `querySelector`.
  1206. *
  1207. * @param {Element|String} [context=document]
  1208. * A DOM element within which to query. Can also be a selector
  1209. * string in which case the first matching element will be used
  1210. * as context. If missing (or no element matches selector), falls
  1211. * back to `document`.
  1212. *
  1213. * @return {Element|null}
  1214. * The element that was found or null.
  1215. */
  1216. var $ = createQuerier('querySelector');
  1217. /**
  1218. * Finds a all DOM elements matching `selector` within the optional
  1219. * `context` of another DOM element (defaulting to `document`).
  1220. *
  1221. * @param {string} selector
  1222. * A valid CSS selector, which will be passed to `querySelectorAll`.
  1223. *
  1224. * @param {Element|String} [context=document]
  1225. * A DOM element within which to query. Can also be a selector
  1226. * string in which case the first matching element will be used
  1227. * as context. If missing (or no element matches selector), falls
  1228. * back to `document`.
  1229. *
  1230. * @return {NodeList}
  1231. * A element list of elements that were found. Will be empty if none
  1232. * were found.
  1233. *
  1234. */
  1235. var $$ = createQuerier('querySelectorAll');
  1236. var Dom = /*#__PURE__*/Object.freeze({
  1237. isReal: isReal,
  1238. isEl: isEl,
  1239. isInFrame: isInFrame,
  1240. createEl: createEl,
  1241. textContent: textContent,
  1242. prependTo: prependTo,
  1243. hasClass: hasClass,
  1244. addClass: addClass,
  1245. removeClass: removeClass,
  1246. toggleClass: toggleClass,
  1247. setAttributes: setAttributes,
  1248. getAttributes: getAttributes,
  1249. getAttribute: getAttribute,
  1250. setAttribute: setAttribute,
  1251. removeAttribute: removeAttribute,
  1252. blockTextSelection: blockTextSelection,
  1253. unblockTextSelection: unblockTextSelection,
  1254. getBoundingClientRect: getBoundingClientRect,
  1255. findPosition: findPosition,
  1256. getPointerPosition: getPointerPosition,
  1257. isTextNode: isTextNode,
  1258. emptyEl: emptyEl,
  1259. normalizeContent: normalizeContent,
  1260. appendContent: appendContent,
  1261. insertContent: insertContent,
  1262. isSingleLeftClick: isSingleLeftClick,
  1263. $: $,
  1264. $$: $$
  1265. });
  1266. /**
  1267. * @file guid.js
  1268. * @module guid
  1269. */
  1270. /**
  1271. * Unique ID for an element or function
  1272. * @type {Number}
  1273. */
  1274. var _guid = 1;
  1275. /**
  1276. * Get a unique auto-incrementing ID by number that has not been returned before.
  1277. *
  1278. * @return {number}
  1279. * A new unique ID.
  1280. */
  1281. function newGUID() {
  1282. return _guid++;
  1283. }
  1284. /**
  1285. * @file dom-data.js
  1286. * @module dom-data
  1287. */
  1288. /**
  1289. * Element Data Store.
  1290. *
  1291. * Allows for binding data to an element without putting it directly on the
  1292. * element. Ex. Event listeners are stored here.
  1293. * (also from jsninja.com, slightly modified and updated for closure compiler)
  1294. *
  1295. * @type {Object}
  1296. * @private
  1297. */
  1298. var elData = {};
  1299. /*
  1300. * Unique attribute name to store an element's guid in
  1301. *
  1302. * @type {String}
  1303. * @constant
  1304. * @private
  1305. */
  1306. var elIdAttr = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now());
  1307. /**
  1308. * Returns the cache object where data for an element is stored
  1309. *
  1310. * @param {Element} el
  1311. * Element to store data for.
  1312. *
  1313. * @return {Object}
  1314. * The cache object for that el that was passed in.
  1315. */
  1316. function getData(el) {
  1317. var id = el[elIdAttr];
  1318. if (!id) {
  1319. id = el[elIdAttr] = newGUID();
  1320. }
  1321. if (!elData[id]) {
  1322. elData[id] = {};
  1323. }
  1324. return elData[id];
  1325. }
  1326. /**
  1327. * Returns whether or not an element has cached data
  1328. *
  1329. * @param {Element} el
  1330. * Check if this element has cached data.
  1331. *
  1332. * @return {boolean}
  1333. * - True if the DOM element has cached data.
  1334. * - False otherwise.
  1335. */
  1336. function hasData(el) {
  1337. var id = el[elIdAttr];
  1338. if (!id) {
  1339. return false;
  1340. }
  1341. return !!Object.getOwnPropertyNames(elData[id]).length;
  1342. }
  1343. /**
  1344. * Delete data for the element from the cache and the guid attr from getElementById
  1345. *
  1346. * @param {Element} el
  1347. * Remove cached data for this element.
  1348. */
  1349. function removeData(el) {
  1350. var id = el[elIdAttr];
  1351. if (!id) {
  1352. return;
  1353. } // Remove all stored data
  1354. delete elData[id]; // Remove the elIdAttr property from the DOM node
  1355. try {
  1356. delete el[elIdAttr];
  1357. } catch (e) {
  1358. if (el.removeAttribute) {
  1359. el.removeAttribute(elIdAttr);
  1360. } else {
  1361. // IE doesn't appear to support removeAttribute on the document element
  1362. el[elIdAttr] = null;
  1363. }
  1364. }
  1365. }
  1366. /**
  1367. * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
  1368. * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
  1369. * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
  1370. * robust as jquery's, so there's probably some differences.
  1371. *
  1372. * @file events.js
  1373. * @module events
  1374. */
  1375. /**
  1376. * Clean up the listener cache and dispatchers
  1377. *
  1378. * @param {Element|Object} elem
  1379. * Element to clean up
  1380. *
  1381. * @param {string} type
  1382. * Type of event to clean up
  1383. */
  1384. function _cleanUpEvents(elem, type) {
  1385. var data = getData(elem); // Remove the events of a particular type if there are none left
  1386. if (data.handlers[type].length === 0) {
  1387. delete data.handlers[type]; // data.handlers[type] = null;
  1388. // Setting to null was causing an error with data.handlers
  1389. // Remove the meta-handler from the element
  1390. if (elem.removeEventListener) {
  1391. elem.removeEventListener(type, data.dispatcher, false);
  1392. } else if (elem.detachEvent) {
  1393. elem.detachEvent('on' + type, data.dispatcher);
  1394. }
  1395. } // Remove the events object if there are no types left
  1396. if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
  1397. delete data.handlers;
  1398. delete data.dispatcher;
  1399. delete data.disabled;
  1400. } // Finally remove the element data if there is no data left
  1401. if (Object.getOwnPropertyNames(data).length === 0) {
  1402. removeData(elem);
  1403. }
  1404. }
  1405. /**
  1406. * Loops through an array of event types and calls the requested method for each type.
  1407. *
  1408. * @param {Function} fn
  1409. * The event method we want to use.
  1410. *
  1411. * @param {Element|Object} elem
  1412. * Element or object to bind listeners to
  1413. *
  1414. * @param {string} type
  1415. * Type of event to bind to.
  1416. *
  1417. * @param {EventTarget~EventListener} callback
  1418. * Event listener.
  1419. */
  1420. function _handleMultipleEvents(fn, elem, types, callback) {
  1421. types.forEach(function (type) {
  1422. // Call the event method for each one of the types
  1423. fn(elem, type, callback);
  1424. });
  1425. }
  1426. /**
  1427. * Fix a native event to have standard property values
  1428. *
  1429. * @param {Object} event
  1430. * Event object to fix.
  1431. *
  1432. * @return {Object}
  1433. * Fixed event object.
  1434. */
  1435. function fixEvent(event) {
  1436. function returnTrue() {
  1437. return true;
  1438. }
  1439. function returnFalse() {
  1440. return false;
  1441. } // Test if fixing up is needed
  1442. // Used to check if !event.stopPropagation instead of isPropagationStopped
  1443. // But native events return true for stopPropagation, but don't have
  1444. // other expected methods like isPropagationStopped. Seems to be a problem
  1445. // with the Javascript Ninja code. So we're just overriding all events now.
  1446. if (!event || !event.isPropagationStopped) {
  1447. var old = event || window$1.event;
  1448. event = {}; // Clone the old object so that we can modify the values event = {};
  1449. // IE8 Doesn't like when you mess with native event properties
  1450. // Firefox returns false for event.hasOwnProperty('type') and other props
  1451. // which makes copying more difficult.
  1452. // TODO: Probably best to create a whitelist of event props
  1453. for (var key in old) {
  1454. // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
  1455. // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
  1456. // and webkitMovementX/Y
  1457. if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
  1458. // Chrome 32+ warns if you try to copy deprecated returnValue, but
  1459. // we still want to if preventDefault isn't supported (IE8).
  1460. if (!(key === 'returnValue' && old.preventDefault)) {
  1461. event[key] = old[key];
  1462. }
  1463. }
  1464. } // The event occurred on this element
  1465. if (!event.target) {
  1466. event.target = event.srcElement || document;
  1467. } // Handle which other element the event is related to
  1468. if (!event.relatedTarget) {
  1469. event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
  1470. } // Stop the default browser action
  1471. event.preventDefault = function () {
  1472. if (old.preventDefault) {
  1473. old.preventDefault();
  1474. }
  1475. event.returnValue = false;
  1476. old.returnValue = false;
  1477. event.defaultPrevented = true;
  1478. };
  1479. event.defaultPrevented = false; // Stop the event from bubbling
  1480. event.stopPropagation = function () {
  1481. if (old.stopPropagation) {
  1482. old.stopPropagation();
  1483. }
  1484. event.cancelBubble = true;
  1485. old.cancelBubble = true;
  1486. event.isPropagationStopped = returnTrue;
  1487. };
  1488. event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
  1489. event.stopImmediatePropagation = function () {
  1490. if (old.stopImmediatePropagation) {
  1491. old.stopImmediatePropagation();
  1492. }
  1493. event.isImmediatePropagationStopped = returnTrue;
  1494. event.stopPropagation();
  1495. };
  1496. event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
  1497. if (event.clientX !== null && event.clientX !== undefined) {
  1498. var doc = document.documentElement;
  1499. var body = document.body;
  1500. event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
  1501. event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
  1502. } // Handle key presses
  1503. event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
  1504. // 0 == left; 1 == middle; 2 == right
  1505. if (event.button !== null && event.button !== undefined) {
  1506. // The following is disabled because it does not pass videojs-standard
  1507. // and... yikes.
  1508. /* eslint-disable */
  1509. event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
  1510. /* eslint-enable */
  1511. }
  1512. } // Returns fixed-up instance
  1513. return event;
  1514. }
  1515. /**
  1516. * Whether passive event listeners are supported
  1517. */
  1518. var _supportsPassive = false;
  1519. (function () {
  1520. try {
  1521. var opts = Object.defineProperty({}, 'passive', {
  1522. get: function get() {
  1523. _supportsPassive = true;
  1524. }
  1525. });
  1526. window$1.addEventListener('test', null, opts);
  1527. window$1.removeEventListener('test', null, opts);
  1528. } catch (e) {// disregard
  1529. }
  1530. })();
  1531. /**
  1532. * Touch events Chrome expects to be passive
  1533. */
  1534. var passiveEvents = ['touchstart', 'touchmove'];
  1535. /**
  1536. * Add an event listener to element
  1537. * It stores the handler function in a separate cache object
  1538. * and adds a generic handler to the element's event,
  1539. * along with a unique id (guid) to the element.
  1540. *
  1541. * @param {Element|Object} elem
  1542. * Element or object to bind listeners to
  1543. *
  1544. * @param {string|string[]} type
  1545. * Type of event to bind to.
  1546. *
  1547. * @param {EventTarget~EventListener} fn
  1548. * Event listener.
  1549. */
  1550. function on(elem, type, fn) {
  1551. if (Array.isArray(type)) {
  1552. return _handleMultipleEvents(on, elem, type, fn);
  1553. }
  1554. var data = getData(elem); // We need a place to store all our handler data
  1555. if (!data.handlers) {
  1556. data.handlers = {};
  1557. }
  1558. if (!data.handlers[type]) {
  1559. data.handlers[type] = [];
  1560. }
  1561. if (!fn.guid) {
  1562. fn.guid = newGUID();
  1563. }
  1564. data.handlers[type].push(fn);
  1565. if (!data.dispatcher) {
  1566. data.disabled = false;
  1567. data.dispatcher = function (event, hash) {
  1568. if (data.disabled) {
  1569. return;
  1570. }
  1571. event = fixEvent(event);
  1572. var handlers = data.handlers[event.type];
  1573. if (handlers) {
  1574. // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
  1575. var handlersCopy = handlers.slice(0);
  1576. for (var m = 0, n = handlersCopy.length; m < n; m++) {
  1577. if (event.isImmediatePropagationStopped()) {
  1578. break;
  1579. } else {
  1580. try {
  1581. handlersCopy[m].call(elem, event, hash);
  1582. } catch (e) {
  1583. log.error(e);
  1584. }
  1585. }
  1586. }
  1587. }
  1588. };
  1589. }
  1590. if (data.handlers[type].length === 1) {
  1591. if (elem.addEventListener) {
  1592. var options = false;
  1593. if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
  1594. options = {
  1595. passive: true
  1596. };
  1597. }
  1598. elem.addEventListener(type, data.dispatcher, options);
  1599. } else if (elem.attachEvent) {
  1600. elem.attachEvent('on' + type, data.dispatcher);
  1601. }
  1602. }
  1603. }
  1604. /**
  1605. * Removes event listeners from an element
  1606. *
  1607. * @param {Element|Object} elem
  1608. * Object to remove listeners from.
  1609. *
  1610. * @param {string|string[]} [type]
  1611. * Type of listener to remove. Don't include to remove all events from element.
  1612. *
  1613. * @param {EventTarget~EventListener} [fn]
  1614. * Specific listener to remove. Don't include to remove listeners for an event
  1615. * type.
  1616. */
  1617. function off(elem, type, fn) {
  1618. // Don't want to add a cache object through getElData if not needed
  1619. if (!hasData(elem)) {
  1620. return;
  1621. }
  1622. var data = getData(elem); // If no events exist, nothing to unbind
  1623. if (!data.handlers) {
  1624. return;
  1625. }
  1626. if (Array.isArray(type)) {
  1627. return _handleMultipleEvents(off, elem, type, fn);
  1628. } // Utility function
  1629. var removeType = function removeType(el, t) {
  1630. data.handlers[t] = [];
  1631. _cleanUpEvents(el, t);
  1632. }; // Are we removing all bound events?
  1633. if (type === undefined) {
  1634. for (var t in data.handlers) {
  1635. if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
  1636. removeType(elem, t);
  1637. }
  1638. }
  1639. return;
  1640. }
  1641. var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
  1642. if (!handlers) {
  1643. return;
  1644. } // If no listener was provided, remove all listeners for type
  1645. if (!fn) {
  1646. removeType(elem, type);
  1647. return;
  1648. } // We're only removing a single handler
  1649. if (fn.guid) {
  1650. for (var n = 0; n < handlers.length; n++) {
  1651. if (handlers[n].guid === fn.guid) {
  1652. handlers.splice(n--, 1);
  1653. }
  1654. }
  1655. }
  1656. _cleanUpEvents(elem, type);
  1657. }
  1658. /**
  1659. * Trigger an event for an element
  1660. *
  1661. * @param {Element|Object} elem
  1662. * Element to trigger an event on
  1663. *
  1664. * @param {EventTarget~Event|string} event
  1665. * A string (the type) or an event object with a type attribute
  1666. *
  1667. * @param {Object} [hash]
  1668. * data hash to pass along with the event
  1669. *
  1670. * @return {boolean|undefined}
  1671. * Returns the opposite of `defaultPrevented` if default was
  1672. * prevented. Otherwise, returns `undefined`
  1673. */
  1674. function trigger(elem, event, hash) {
  1675. // Fetches element data and a reference to the parent (for bubbling).
  1676. // Don't want to add a data object to cache for every parent,
  1677. // so checking hasElData first.
  1678. var elemData = hasData(elem) ? getData(elem) : {};
  1679. var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
  1680. // handler;
  1681. // If an event name was passed as a string, creates an event out of it
  1682. if (typeof event === 'string') {
  1683. event = {
  1684. type: event,
  1685. target: elem
  1686. };
  1687. } else if (!event.target) {
  1688. event.target = elem;
  1689. } // Normalizes the event properties.
  1690. event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
  1691. if (elemData.dispatcher) {
  1692. elemData.dispatcher.call(elem, event, hash);
  1693. } // Unless explicitly stopped or the event does not bubble (e.g. media events)
  1694. // recursively calls this function to bubble the event up the DOM.
  1695. if (parent && !event.isPropagationStopped() && event.bubbles === true) {
  1696. trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
  1697. } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
  1698. var targetData = getData(event.target); // Checks if the target has a default action for this event.
  1699. if (event.target[event.type]) {
  1700. // Temporarily disables event dispatching on the target as we have already executed the handler.
  1701. targetData.disabled = true; // Executes the default action.
  1702. if (typeof event.target[event.type] === 'function') {
  1703. event.target[event.type]();
  1704. } // Re-enables event dispatching.
  1705. targetData.disabled = false;
  1706. }
  1707. } // Inform the triggerer if the default was prevented by returning false
  1708. return !event.defaultPrevented;
  1709. }
  1710. /**
  1711. * Trigger a listener only once for an event.
  1712. *
  1713. * @param {Element|Object} elem
  1714. * Element or object to bind to.
  1715. *
  1716. * @param {string|string[]} type
  1717. * Name/type of event
  1718. *
  1719. * @param {Event~EventListener} fn
  1720. * Event listener function
  1721. */
  1722. function one(elem, type, fn) {
  1723. if (Array.isArray(type)) {
  1724. return _handleMultipleEvents(one, elem, type, fn);
  1725. }
  1726. var func = function func() {
  1727. off(elem, type, func);
  1728. fn.apply(this, arguments);
  1729. }; // copy the guid to the new function so it can removed using the original function's ID
  1730. func.guid = fn.guid = fn.guid || newGUID();
  1731. on(elem, type, func);
  1732. }
  1733. /**
  1734. * Trigger a listener only once and then turn if off for all
  1735. * configured events
  1736. *
  1737. * @param {Element|Object} elem
  1738. * Element or object to bind to.
  1739. *
  1740. * @param {string|string[]} type
  1741. * Name/type of event
  1742. *
  1743. * @param {Event~EventListener} fn
  1744. * Event listener function
  1745. */
  1746. function any(elem, type, fn) {
  1747. var func = function func() {
  1748. off(elem, type, func);
  1749. fn.apply(this, arguments);
  1750. }; // copy the guid to the new function so it can removed using the original function's ID
  1751. func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything
  1752. on(elem, type, func);
  1753. }
  1754. var Events = /*#__PURE__*/Object.freeze({
  1755. fixEvent: fixEvent,
  1756. on: on,
  1757. off: off,
  1758. trigger: trigger,
  1759. one: one,
  1760. any: any
  1761. });
  1762. /**
  1763. * @file setup.js - Functions for setting up a player without
  1764. * user interaction based on the data-setup `attribute` of the video tag.
  1765. *
  1766. * @module setup
  1767. */
  1768. var _windowLoaded = false;
  1769. var videojs;
  1770. /**
  1771. * Set up any tags that have a data-setup `attribute` when the player is started.
  1772. */
  1773. var autoSetup = function autoSetup() {
  1774. // Protect against breakage in non-browser environments and check global autoSetup option.
  1775. if (!isReal() || videojs.options.autoSetup === false) {
  1776. return;
  1777. }
  1778. var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
  1779. var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
  1780. var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
  1781. var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
  1782. if (mediaEls && mediaEls.length > 0) {
  1783. for (var i = 0, e = mediaEls.length; i < e; i++) {
  1784. var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
  1785. if (mediaEl && mediaEl.getAttribute) {
  1786. // Make sure this player hasn't already been set up.
  1787. if (mediaEl.player === undefined) {
  1788. var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
  1789. // We only auto-setup if they've added the data-setup attr.
  1790. if (options !== null) {
  1791. // Create new video.js instance.
  1792. videojs(mediaEl);
  1793. }
  1794. } // If getAttribute isn't defined, we need to wait for the DOM.
  1795. } else {
  1796. autoSetupTimeout(1);
  1797. break;
  1798. }
  1799. } // No videos were found, so keep looping unless page is finished loading.
  1800. } else if (!_windowLoaded) {
  1801. autoSetupTimeout(1);
  1802. }
  1803. };
  1804. /**
  1805. * Wait until the page is loaded before running autoSetup. This will be called in
  1806. * autoSetup if `hasLoaded` returns false.
  1807. *
  1808. * @param {number} wait
  1809. * How long to wait in ms
  1810. *
  1811. * @param {module:videojs} [vjs]
  1812. * The videojs library function
  1813. */
  1814. function autoSetupTimeout(wait, vjs) {
  1815. if (vjs) {
  1816. videojs = vjs;
  1817. }
  1818. window$1.setTimeout(autoSetup, wait);
  1819. }
  1820. if (isReal() && document.readyState === 'complete') {
  1821. _windowLoaded = true;
  1822. } else {
  1823. /**
  1824. * Listen for the load event on window, and set _windowLoaded to true.
  1825. *
  1826. * @listens load
  1827. */
  1828. one(window$1, 'load', function () {
  1829. _windowLoaded = true;
  1830. });
  1831. }
  1832. /**
  1833. * @file stylesheet.js
  1834. * @module stylesheet
  1835. */
  1836. /**
  1837. * Create a DOM syle element given a className for it.
  1838. *
  1839. * @param {string} className
  1840. * The className to add to the created style element.
  1841. *
  1842. * @return {Element}
  1843. * The element that was created.
  1844. */
  1845. var createStyleElement = function createStyleElement(className) {
  1846. var style = document.createElement('style');
  1847. style.className = className;
  1848. return style;
  1849. };
  1850. /**
  1851. * Add text to a DOM element.
  1852. *
  1853. * @param {Element} el
  1854. * The Element to add text content to.
  1855. *
  1856. * @param {string} content
  1857. * The text to add to the element.
  1858. */
  1859. var setTextContent = function setTextContent(el, content) {
  1860. if (el.styleSheet) {
  1861. el.styleSheet.cssText = content;
  1862. } else {
  1863. el.textContent = content;
  1864. }
  1865. };
  1866. /**
  1867. * @file fn.js
  1868. * @module fn
  1869. */
  1870. /**
  1871. * Bind (a.k.a proxy or context). A simple method for changing the context of
  1872. * a function.
  1873. *
  1874. * It also stores a unique id on the function so it can be easily removed from
  1875. * events.
  1876. *
  1877. * @function
  1878. * @param {Mixed} context
  1879. * The object to bind as scope.
  1880. *
  1881. * @param {Function} fn
  1882. * The function to be bound to a scope.
  1883. *
  1884. * @param {number} [uid]
  1885. * An optional unique ID for the function to be set
  1886. *
  1887. * @return {Function}
  1888. * The new function that will be bound into the context given
  1889. */
  1890. var bind = function bind(context, fn, uid) {
  1891. // Make sure the function has a unique ID
  1892. if (!fn.guid) {
  1893. fn.guid = newGUID();
  1894. } // Create the new function that changes the context
  1895. var bound = function bound() {
  1896. return fn.apply(context, arguments);
  1897. }; // Allow for the ability to individualize this function
  1898. // Needed in the case where multiple objects might share the same prototype
  1899. // IF both items add an event listener with the same function, then you try to remove just one
  1900. // it will remove both because they both have the same guid.
  1901. // when using this, you need to use the bind method when you remove the listener as well.
  1902. // currently used in text tracks
  1903. bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
  1904. return bound;
  1905. };
  1906. /**
  1907. * Wraps the given function, `fn`, with a new function that only invokes `fn`
  1908. * at most once per every `wait` milliseconds.
  1909. *
  1910. * @function
  1911. * @param {Function} fn
  1912. * The function to be throttled.
  1913. *
  1914. * @param {number} wait
  1915. * The number of milliseconds by which to throttle.
  1916. *
  1917. * @return {Function}
  1918. */
  1919. var throttle = function throttle(fn, wait) {
  1920. var last = window$1.performance.now();
  1921. var throttled = function throttled() {
  1922. var now = window$1.performance.now();
  1923. if (now - last >= wait) {
  1924. fn.apply(void 0, arguments);
  1925. last = now;
  1926. }
  1927. };
  1928. return throttled;
  1929. };
  1930. /**
  1931. * Creates a debounced function that delays invoking `func` until after `wait`
  1932. * milliseconds have elapsed since the last time the debounced function was
  1933. * invoked.
  1934. *
  1935. * Inspired by lodash and underscore implementations.
  1936. *
  1937. * @function
  1938. * @param {Function} func
  1939. * The function to wrap with debounce behavior.
  1940. *
  1941. * @param {number} wait
  1942. * The number of milliseconds to wait after the last invocation.
  1943. *
  1944. * @param {boolean} [immediate]
  1945. * Whether or not to invoke the function immediately upon creation.
  1946. *
  1947. * @param {Object} [context=window]
  1948. * The "context" in which the debounced function should debounce. For
  1949. * example, if this function should be tied to a Video.js player,
  1950. * the player can be passed here. Alternatively, defaults to the
  1951. * global `window` object.
  1952. *
  1953. * @return {Function}
  1954. * A debounced function.
  1955. */
  1956. var debounce = function debounce(func, wait, immediate, context) {
  1957. if (context === void 0) {
  1958. context = window$1;
  1959. }
  1960. var timeout;
  1961. var cancel = function cancel() {
  1962. context.clearTimeout(timeout);
  1963. timeout = null;
  1964. };
  1965. /* eslint-disable consistent-this */
  1966. var debounced = function debounced() {
  1967. var self = this;
  1968. var args = arguments;
  1969. var _later = function later() {
  1970. timeout = null;
  1971. _later = null;
  1972. if (!immediate) {
  1973. func.apply(self, args);
  1974. }
  1975. };
  1976. if (!timeout && immediate) {
  1977. func.apply(self, args);
  1978. }
  1979. context.clearTimeout(timeout);
  1980. timeout = context.setTimeout(_later, wait);
  1981. };
  1982. /* eslint-enable consistent-this */
  1983. debounced.cancel = cancel;
  1984. return debounced;
  1985. };
  1986. /**
  1987. * @file src/js/event-target.js
  1988. */
  1989. /**
  1990. * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
  1991. * adds shorthand functions that wrap around lengthy functions. For example:
  1992. * the `on` function is a wrapper around `addEventListener`.
  1993. *
  1994. * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
  1995. * @class EventTarget
  1996. */
  1997. var EventTarget = function EventTarget() {};
  1998. /**
  1999. * A Custom DOM event.
  2000. *
  2001. * @typedef {Object} EventTarget~Event
  2002. * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
  2003. */
  2004. /**
  2005. * All event listeners should follow the following format.
  2006. *
  2007. * @callback EventTarget~EventListener
  2008. * @this {EventTarget}
  2009. *
  2010. * @param {EventTarget~Event} event
  2011. * the event that triggered this function
  2012. *
  2013. * @param {Object} [hash]
  2014. * hash of data sent during the event
  2015. */
  2016. /**
  2017. * An object containing event names as keys and booleans as values.
  2018. *
  2019. * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
  2020. * will have extra functionality. See that function for more information.
  2021. *
  2022. * @property EventTarget.prototype.allowedEvents_
  2023. * @private
  2024. */
  2025. EventTarget.prototype.allowedEvents_ = {};
  2026. /**
  2027. * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
  2028. * function that will get called when an event with a certain name gets triggered.
  2029. *
  2030. * @param {string|string[]} type
  2031. * An event name or an array of event names.
  2032. *
  2033. * @param {EventTarget~EventListener} fn
  2034. * The function to call with `EventTarget`s
  2035. */
  2036. EventTarget.prototype.on = function (type, fn) {
  2037. // Remove the addEventListener alias before calling Events.on
  2038. // so we don't get into an infinite type loop
  2039. var ael = this.addEventListener;
  2040. this.addEventListener = function () {};
  2041. on(this, type, fn);
  2042. this.addEventListener = ael;
  2043. };
  2044. /**
  2045. * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
  2046. * the standard DOM API.
  2047. *
  2048. * @function
  2049. * @see {@link EventTarget#on}
  2050. */
  2051. EventTarget.prototype.addEventListener = EventTarget.prototype.on;
  2052. /**
  2053. * Removes an `event listener` for a specific event from an instance of `EventTarget`.
  2054. * This makes it so that the `event listener` will no longer get called when the
  2055. * named event happens.
  2056. *
  2057. * @param {string|string[]} type
  2058. * An event name or an array of event names.
  2059. *
  2060. * @param {EventTarget~EventListener} fn
  2061. * The function to remove.
  2062. */
  2063. EventTarget.prototype.off = function (type, fn) {
  2064. off(this, type, fn);
  2065. };
  2066. /**
  2067. * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
  2068. * the standard DOM API.
  2069. *
  2070. * @function
  2071. * @see {@link EventTarget#off}
  2072. */
  2073. EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
  2074. /**
  2075. * This function will add an `event listener` that gets triggered only once. After the
  2076. * first trigger it will get removed. This is like adding an `event listener`
  2077. * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
  2078. *
  2079. * @param {string|string[]} type
  2080. * An event name or an array of event names.
  2081. *
  2082. * @param {EventTarget~EventListener} fn
  2083. * The function to be called once for each event name.
  2084. */
  2085. EventTarget.prototype.one = function (type, fn) {
  2086. // Remove the addEventListener aliasing Events.on
  2087. // so we don't get into an infinite type loop
  2088. var ael = this.addEventListener;
  2089. this.addEventListener = function () {};
  2090. one(this, type, fn);
  2091. this.addEventListener = ael;
  2092. };
  2093. EventTarget.prototype.any = function (type, fn) {
  2094. // Remove the addEventListener aliasing Events.on
  2095. // so we don't get into an infinite type loop
  2096. var ael = this.addEventListener;
  2097. this.addEventListener = function () {};
  2098. any(this, type, fn);
  2099. this.addEventListener = ael;
  2100. };
  2101. /**
  2102. * This function causes an event to happen. This will then cause any `event listeners`
  2103. * that are waiting for that event, to get called. If there are no `event listeners`
  2104. * for an event then nothing will happen.
  2105. *
  2106. * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
  2107. * Trigger will also call the `on` + `uppercaseEventName` function.
  2108. *
  2109. * Example:
  2110. * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
  2111. * `onClick` if it exists.
  2112. *
  2113. * @param {string|EventTarget~Event|Object} event
  2114. * The name of the event, an `Event`, or an object with a key of type set to
  2115. * an event name.
  2116. */
  2117. EventTarget.prototype.trigger = function (event) {
  2118. var type = event.type || event; // deprecation
  2119. // In a future version we should default target to `this`
  2120. // similar to how we default the target to `elem` in
  2121. // `Events.trigger`. Right now the default `target` will be
  2122. // `document` due to the `Event.fixEvent` call.
  2123. if (typeof event === 'string') {
  2124. event = {
  2125. type: type
  2126. };
  2127. }
  2128. event = fixEvent(event);
  2129. if (this.allowedEvents_[type] && this['on' + type]) {
  2130. this['on' + type](event);
  2131. }
  2132. trigger(this, event);
  2133. };
  2134. /**
  2135. * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
  2136. * the standard DOM API.
  2137. *
  2138. * @function
  2139. * @see {@link EventTarget#trigger}
  2140. */
  2141. EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
  2142. var EVENT_MAP;
  2143. EventTarget.prototype.queueTrigger = function (event) {
  2144. var _this = this;
  2145. // only set up EVENT_MAP if it'll be used
  2146. if (!EVENT_MAP) {
  2147. EVENT_MAP = new Map();
  2148. }
  2149. var type = event.type || event;
  2150. var map = EVENT_MAP.get(this);
  2151. if (!map) {
  2152. map = new Map();
  2153. EVENT_MAP.set(this, map);
  2154. }
  2155. var oldTimeout = map.get(type);
  2156. map["delete"](type);
  2157. window$1.clearTimeout(oldTimeout);
  2158. var timeout = window$1.setTimeout(function () {
  2159. // if we cleared out all timeouts for the current target, delete its map
  2160. if (map.size === 0) {
  2161. map = null;
  2162. EVENT_MAP["delete"](_this);
  2163. }
  2164. _this.trigger(event);
  2165. }, 0);
  2166. map.set(type, timeout);
  2167. };
  2168. /**
  2169. * @file mixins/evented.js
  2170. * @module evented
  2171. */
  2172. /**
  2173. * Returns whether or not an object has had the evented mixin applied.
  2174. *
  2175. * @param {Object} object
  2176. * An object to test.
  2177. *
  2178. * @return {boolean}
  2179. * Whether or not the object appears to be evented.
  2180. */
  2181. var isEvented = function isEvented(object) {
  2182. return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
  2183. return typeof object[k] === 'function';
  2184. });
  2185. };
  2186. /**
  2187. * Adds a callback to run after the evented mixin applied.
  2188. *
  2189. * @param {Object} object
  2190. * An object to Add
  2191. * @param {Function} callback
  2192. * The callback to run.
  2193. */
  2194. var addEventedCallback = function addEventedCallback(target, callback) {
  2195. if (isEvented(target)) {
  2196. callback();
  2197. } else {
  2198. if (!target.eventedCallbacks) {
  2199. target.eventedCallbacks = [];
  2200. }
  2201. target.eventedCallbacks.push(callback);
  2202. }
  2203. };
  2204. /**
  2205. * Whether a value is a valid event type - non-empty string or array.
  2206. *
  2207. * @private
  2208. * @param {string|Array} type
  2209. * The type value to test.
  2210. *
  2211. * @return {boolean}
  2212. * Whether or not the type is a valid event type.
  2213. */
  2214. var isValidEventType = function isValidEventType(type) {
  2215. return (// The regex here verifies that the `type` contains at least one non-
  2216. // whitespace character.
  2217. typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
  2218. );
  2219. };
  2220. /**
  2221. * Validates a value to determine if it is a valid event target. Throws if not.
  2222. *
  2223. * @private
  2224. * @throws {Error}
  2225. * If the target does not appear to be a valid event target.
  2226. *
  2227. * @param {Object} target
  2228. * The object to test.
  2229. */
  2230. var validateTarget = function validateTarget(target) {
  2231. if (!target.nodeName && !isEvented(target)) {
  2232. throw new Error('Invalid target; must be a DOM node or evented object.');
  2233. }
  2234. };
  2235. /**
  2236. * Validates a value to determine if it is a valid event target. Throws if not.
  2237. *
  2238. * @private
  2239. * @throws {Error}
  2240. * If the type does not appear to be a valid event type.
  2241. *
  2242. * @param {string|Array} type
  2243. * The type to test.
  2244. */
  2245. var validateEventType = function validateEventType(type) {
  2246. if (!isValidEventType(type)) {
  2247. throw new Error('Invalid event type; must be a non-empty string or array.');
  2248. }
  2249. };
  2250. /**
  2251. * Validates a value to determine if it is a valid listener. Throws if not.
  2252. *
  2253. * @private
  2254. * @throws {Error}
  2255. * If the listener is not a function.
  2256. *
  2257. * @param {Function} listener
  2258. * The listener to test.
  2259. */
  2260. var validateListener = function validateListener(listener) {
  2261. if (typeof listener !== 'function') {
  2262. throw new Error('Invalid listener; must be a function.');
  2263. }
  2264. };
  2265. /**
  2266. * Takes an array of arguments given to `on()` or `one()`, validates them, and
  2267. * normalizes them into an object.
  2268. *
  2269. * @private
  2270. * @param {Object} self
  2271. * The evented object on which `on()` or `one()` was called. This
  2272. * object will be bound as the `this` value for the listener.
  2273. *
  2274. * @param {Array} args
  2275. * An array of arguments passed to `on()` or `one()`.
  2276. *
  2277. * @return {Object}
  2278. * An object containing useful values for `on()` or `one()` calls.
  2279. */
  2280. var normalizeListenArgs = function normalizeListenArgs(self, args) {
  2281. // If the number of arguments is less than 3, the target is always the
  2282. // evented object itself.
  2283. var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
  2284. var target;
  2285. var type;
  2286. var listener;
  2287. if (isTargetingSelf) {
  2288. target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
  2289. // the evented object itself.
  2290. if (args.length >= 3) {
  2291. args.shift();
  2292. }
  2293. type = args[0];
  2294. listener = args[1];
  2295. } else {
  2296. target = args[0];
  2297. type = args[1];
  2298. listener = args[2];
  2299. }
  2300. validateTarget(target);
  2301. validateEventType(type);
  2302. validateListener(listener);
  2303. listener = bind(self, listener);
  2304. return {
  2305. isTargetingSelf: isTargetingSelf,
  2306. target: target,
  2307. type: type,
  2308. listener: listener
  2309. };
  2310. };
  2311. /**
  2312. * Adds the listener to the event type(s) on the target, normalizing for
  2313. * the type of target.
  2314. *
  2315. * @private
  2316. * @param {Element|Object} target
  2317. * A DOM node or evented object.
  2318. *
  2319. * @param {string} method
  2320. * The event binding method to use ("on" or "one").
  2321. *
  2322. * @param {string|Array} type
  2323. * One or more event type(s).
  2324. *
  2325. * @param {Function} listener
  2326. * A listener function.
  2327. */
  2328. var listen = function listen(target, method, type, listener) {
  2329. validateTarget(target);
  2330. if (target.nodeName) {
  2331. Events[method](target, type, listener);
  2332. } else {
  2333. target[method](type, listener);
  2334. }
  2335. };
  2336. /**
  2337. * Contains methods that provide event capabilities to an object which is passed
  2338. * to {@link module:evented|evented}.
  2339. *
  2340. * @mixin EventedMixin
  2341. */
  2342. var EventedMixin = {
  2343. /**
  2344. * Add a listener to an event (or events) on this object or another evented
  2345. * object.
  2346. *
  2347. * @param {string|Array|Element|Object} targetOrType
  2348. * If this is a string or array, it represents the event type(s)
  2349. * that will trigger the listener.
  2350. *
  2351. * Another evented object can be passed here instead, which will
  2352. * cause the listener to listen for events on _that_ object.
  2353. *
  2354. * In either case, the listener's `this` value will be bound to
  2355. * this object.
  2356. *
  2357. * @param {string|Array|Function} typeOrListener
  2358. * If the first argument was a string or array, this should be the
  2359. * listener function. Otherwise, this is a string or array of event
  2360. * type(s).
  2361. *
  2362. * @param {Function} [listener]
  2363. * If the first argument was another evented object, this will be
  2364. * the listener function.
  2365. */
  2366. on: function on() {
  2367. var _this = this;
  2368. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  2369. args[_key] = arguments[_key];
  2370. }
  2371. var _normalizeListenArgs = normalizeListenArgs(this, args),
  2372. isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
  2373. target = _normalizeListenArgs.target,
  2374. type = _normalizeListenArgs.type,
  2375. listener = _normalizeListenArgs.listener;
  2376. listen(target, 'on', type, listener); // If this object is listening to another evented object.
  2377. if (!isTargetingSelf) {
  2378. // If this object is disposed, remove the listener.
  2379. var removeListenerOnDispose = function removeListenerOnDispose() {
  2380. return _this.off(target, type, listener);
  2381. }; // Use the same function ID as the listener so we can remove it later it
  2382. // using the ID of the original listener.
  2383. removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
  2384. // that if the target is disposed BEFORE this object, we remove the
  2385. // removal listener that was just added. Otherwise, we create a memory leak.
  2386. var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
  2387. return _this.off('dispose', removeListenerOnDispose);
  2388. }; // Use the same function ID as the listener so we can remove it later
  2389. // it using the ID of the original listener.
  2390. removeRemoverOnTargetDispose.guid = listener.guid;
  2391. listen(this, 'on', 'dispose', removeListenerOnDispose);
  2392. listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
  2393. }
  2394. },
  2395. /**
  2396. * Add a listener to an event (or events) on this object or another evented
  2397. * object. The listener will be called once per event and then removed.
  2398. *
  2399. * @param {string|Array|Element|Object} targetOrType
  2400. * If this is a string or array, it represents the event type(s)
  2401. * that will trigger the listener.
  2402. *
  2403. * Another evented object can be passed here instead, which will
  2404. * cause the listener to listen for events on _that_ object.
  2405. *
  2406. * In either case, the listener's `this` value will be bound to
  2407. * this object.
  2408. *
  2409. * @param {string|Array|Function} typeOrListener
  2410. * If the first argument was a string or array, this should be the
  2411. * listener function. Otherwise, this is a string or array of event
  2412. * type(s).
  2413. *
  2414. * @param {Function} [listener]
  2415. * If the first argument was another evented object, this will be
  2416. * the listener function.
  2417. */
  2418. one: function one() {
  2419. var _this2 = this;
  2420. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  2421. args[_key2] = arguments[_key2];
  2422. }
  2423. var _normalizeListenArgs2 = normalizeListenArgs(this, args),
  2424. isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
  2425. target = _normalizeListenArgs2.target,
  2426. type = _normalizeListenArgs2.type,
  2427. listener = _normalizeListenArgs2.listener; // Targeting this evented object.
  2428. if (isTargetingSelf) {
  2429. listen(target, 'one', type, listener); // Targeting another evented object.
  2430. } else {
  2431. // TODO: This wrapper is incorrect! It should only
  2432. // remove the wrapper for the event type that called it.
  2433. // Instead all listners are removed on the first trigger!
  2434. // see https://github.com/videojs/video.js/issues/5962
  2435. var wrapper = function wrapper() {
  2436. _this2.off(target, type, wrapper);
  2437. for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  2438. largs[_key3] = arguments[_key3];
  2439. }
  2440. listener.apply(null, largs);
  2441. }; // Use the same function ID as the listener so we can remove it later
  2442. // it using the ID of the original listener.
  2443. wrapper.guid = listener.guid;
  2444. listen(target, 'one', type, wrapper);
  2445. }
  2446. },
  2447. /**
  2448. * Add a listener to an event (or events) on this object or another evented
  2449. * object. The listener will only be called once for the first event that is triggered
  2450. * then removed.
  2451. *
  2452. * @param {string|Array|Element|Object} targetOrType
  2453. * If this is a string or array, it represents the event type(s)
  2454. * that will trigger the listener.
  2455. *
  2456. * Another evented object can be passed here instead, which will
  2457. * cause the listener to listen for events on _that_ object.
  2458. *
  2459. * In either case, the listener's `this` value will be bound to
  2460. * this object.
  2461. *
  2462. * @param {string|Array|Function} typeOrListener
  2463. * If the first argument was a string or array, this should be the
  2464. * listener function. Otherwise, this is a string or array of event
  2465. * type(s).
  2466. *
  2467. * @param {Function} [listener]
  2468. * If the first argument was another evented object, this will be
  2469. * the listener function.
  2470. */
  2471. any: function any() {
  2472. var _this3 = this;
  2473. for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  2474. args[_key4] = arguments[_key4];
  2475. }
  2476. var _normalizeListenArgs3 = normalizeListenArgs(this, args),
  2477. isTargetingSelf = _normalizeListenArgs3.isTargetingSelf,
  2478. target = _normalizeListenArgs3.target,
  2479. type = _normalizeListenArgs3.type,
  2480. listener = _normalizeListenArgs3.listener; // Targeting this evented object.
  2481. if (isTargetingSelf) {
  2482. listen(target, 'any', type, listener); // Targeting another evented object.
  2483. } else {
  2484. var wrapper = function wrapper() {
  2485. _this3.off(target, type, wrapper);
  2486. for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
  2487. largs[_key5] = arguments[_key5];
  2488. }
  2489. listener.apply(null, largs);
  2490. }; // Use the same function ID as the listener so we can remove it later
  2491. // it using the ID of the original listener.
  2492. wrapper.guid = listener.guid;
  2493. listen(target, 'any', type, wrapper);
  2494. }
  2495. },
  2496. /**
  2497. * Removes listener(s) from event(s) on an evented object.
  2498. *
  2499. * @param {string|Array|Element|Object} [targetOrType]
  2500. * If this is a string or array, it represents the event type(s).
  2501. *
  2502. * Another evented object can be passed here instead, in which case
  2503. * ALL 3 arguments are _required_.
  2504. *
  2505. * @param {string|Array|Function} [typeOrListener]
  2506. * If the first argument was a string or array, this may be the
  2507. * listener function. Otherwise, this is a string or array of event
  2508. * type(s).
  2509. *
  2510. * @param {Function} [listener]
  2511. * If the first argument was another evented object, this will be
  2512. * the listener function; otherwise, _all_ listeners bound to the
  2513. * event type(s) will be removed.
  2514. */
  2515. off: function off$1(targetOrType, typeOrListener, listener) {
  2516. // Targeting this evented object.
  2517. if (!targetOrType || isValidEventType(targetOrType)) {
  2518. off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
  2519. } else {
  2520. var target = targetOrType;
  2521. var type = typeOrListener; // Fail fast and in a meaningful way!
  2522. validateTarget(target);
  2523. validateEventType(type);
  2524. validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used
  2525. listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
  2526. // the same guid as the event listener in on().
  2527. this.off('dispose', listener);
  2528. if (target.nodeName) {
  2529. off(target, type, listener);
  2530. off(target, 'dispose', listener);
  2531. } else if (isEvented(target)) {
  2532. target.off(type, listener);
  2533. target.off('dispose', listener);
  2534. }
  2535. }
  2536. },
  2537. /**
  2538. * Fire an event on this evented object, causing its listeners to be called.
  2539. *
  2540. * @param {string|Object} event
  2541. * An event type or an object with a type property.
  2542. *
  2543. * @param {Object} [hash]
  2544. * An additional object to pass along to listeners.
  2545. *
  2546. * @return {boolean}
  2547. * Whether or not the default behavior was prevented.
  2548. */
  2549. trigger: function trigger$1(event, hash) {
  2550. return trigger(this.eventBusEl_, event, hash);
  2551. }
  2552. };
  2553. /**
  2554. * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
  2555. *
  2556. * @param {Object} target
  2557. * The object to which to add event methods.
  2558. *
  2559. * @param {Object} [options={}]
  2560. * Options for customizing the mixin behavior.
  2561. *
  2562. * @param {string} [options.eventBusKey]
  2563. * By default, adds a `eventBusEl_` DOM element to the target object,
  2564. * which is used as an event bus. If the target object already has a
  2565. * DOM element that should be used, pass its key here.
  2566. *
  2567. * @return {Object}
  2568. * The target object.
  2569. */
  2570. function evented(target, options) {
  2571. if (options === void 0) {
  2572. options = {};
  2573. }
  2574. var _options = options,
  2575. eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
  2576. if (eventBusKey) {
  2577. if (!target[eventBusKey].nodeName) {
  2578. throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
  2579. }
  2580. target.eventBusEl_ = target[eventBusKey];
  2581. } else {
  2582. target.eventBusEl_ = createEl('span', {
  2583. className: 'vjs-event-bus'
  2584. });
  2585. }
  2586. assign(target, EventedMixin);
  2587. if (target.eventedCallbacks) {
  2588. target.eventedCallbacks.forEach(function (callback) {
  2589. callback();
  2590. });
  2591. } // When any evented object is disposed, it removes all its listeners.
  2592. target.on('dispose', function () {
  2593. target.off();
  2594. window$1.setTimeout(function () {
  2595. target.eventBusEl_ = null;
  2596. }, 0);
  2597. });
  2598. return target;
  2599. }
  2600. /**
  2601. * @file mixins/stateful.js
  2602. * @module stateful
  2603. */
  2604. /**
  2605. * Contains methods that provide statefulness to an object which is passed
  2606. * to {@link module:stateful}.
  2607. *
  2608. * @mixin StatefulMixin
  2609. */
  2610. var StatefulMixin = {
  2611. /**
  2612. * A hash containing arbitrary keys and values representing the state of
  2613. * the object.
  2614. *
  2615. * @type {Object}
  2616. */
  2617. state: {},
  2618. /**
  2619. * Set the state of an object by mutating its
  2620. * {@link module:stateful~StatefulMixin.state|state} object in place.
  2621. *
  2622. * @fires module:stateful~StatefulMixin#statechanged
  2623. * @param {Object|Function} stateUpdates
  2624. * A new set of properties to shallow-merge into the plugin state.
  2625. * Can be a plain object or a function returning a plain object.
  2626. *
  2627. * @return {Object|undefined}
  2628. * An object containing changes that occurred. If no changes
  2629. * occurred, returns `undefined`.
  2630. */
  2631. setState: function setState(stateUpdates) {
  2632. var _this = this;
  2633. // Support providing the `stateUpdates` state as a function.
  2634. if (typeof stateUpdates === 'function') {
  2635. stateUpdates = stateUpdates();
  2636. }
  2637. var changes;
  2638. each(stateUpdates, function (value, key) {
  2639. // Record the change if the value is different from what's in the
  2640. // current state.
  2641. if (_this.state[key] !== value) {
  2642. changes = changes || {};
  2643. changes[key] = {
  2644. from: _this.state[key],
  2645. to: value
  2646. };
  2647. }
  2648. _this.state[key] = value;
  2649. }); // Only trigger "statechange" if there were changes AND we have a trigger
  2650. // function. This allows us to not require that the target object be an
  2651. // evented object.
  2652. if (changes && isEvented(this)) {
  2653. /**
  2654. * An event triggered on an object that is both
  2655. * {@link module:stateful|stateful} and {@link module:evented|evented}
  2656. * indicating that its state has changed.
  2657. *
  2658. * @event module:stateful~StatefulMixin#statechanged
  2659. * @type {Object}
  2660. * @property {Object} changes
  2661. * A hash containing the properties that were changed and
  2662. * the values they were changed `from` and `to`.
  2663. */
  2664. this.trigger({
  2665. changes: changes,
  2666. type: 'statechanged'
  2667. });
  2668. }
  2669. return changes;
  2670. }
  2671. };
  2672. /**
  2673. * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
  2674. * object.
  2675. *
  2676. * If the target object is {@link module:evented|evented} and has a
  2677. * `handleStateChanged` method, that method will be automatically bound to the
  2678. * `statechanged` event on itself.
  2679. *
  2680. * @param {Object} target
  2681. * The object to be made stateful.
  2682. *
  2683. * @param {Object} [defaultState]
  2684. * A default set of properties to populate the newly-stateful object's
  2685. * `state` property.
  2686. *
  2687. * @return {Object}
  2688. * Returns the `target`.
  2689. */
  2690. function stateful(target, defaultState) {
  2691. assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
  2692. // added in that step.
  2693. target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
  2694. if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
  2695. target.on('statechanged', target.handleStateChanged);
  2696. }
  2697. return target;
  2698. }
  2699. /**
  2700. * @file to-title-case.js
  2701. * @module to-title-case
  2702. */
  2703. /**
  2704. * Uppercase the first letter of a string.
  2705. *
  2706. * @param {string} string
  2707. * String to be uppercased
  2708. *
  2709. * @return {string}
  2710. * The string with an uppercased first letter
  2711. */
  2712. function toTitleCase(string) {
  2713. if (typeof string !== 'string') {
  2714. return string;
  2715. }
  2716. return string.charAt(0).toUpperCase() + string.slice(1);
  2717. }
  2718. /**
  2719. * Compares the TitleCase versions of the two strings for equality.
  2720. *
  2721. * @param {string} str1
  2722. * The first string to compare
  2723. *
  2724. * @param {string} str2
  2725. * The second string to compare
  2726. *
  2727. * @return {boolean}
  2728. * Whether the TitleCase versions of the strings are equal
  2729. */
  2730. function titleCaseEquals(str1, str2) {
  2731. return toTitleCase(str1) === toTitleCase(str2);
  2732. }
  2733. /**
  2734. * @file merge-options.js
  2735. * @module merge-options
  2736. */
  2737. /**
  2738. * Merge two objects recursively.
  2739. *
  2740. * Performs a deep merge like
  2741. * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
  2742. * plain objects (not arrays, elements, or anything else).
  2743. *
  2744. * Non-plain object values will be copied directly from the right-most
  2745. * argument.
  2746. *
  2747. * @static
  2748. * @param {Object[]} sources
  2749. * One or more objects to merge into a new object.
  2750. *
  2751. * @return {Object}
  2752. * A new object that is the merged result of all sources.
  2753. */
  2754. function mergeOptions() {
  2755. var result = {};
  2756. for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
  2757. sources[_key] = arguments[_key];
  2758. }
  2759. sources.forEach(function (source) {
  2760. if (!source) {
  2761. return;
  2762. }
  2763. each(source, function (value, key) {
  2764. if (!isPlain(value)) {
  2765. result[key] = value;
  2766. return;
  2767. }
  2768. if (!isPlain(result[key])) {
  2769. result[key] = {};
  2770. }
  2771. result[key] = mergeOptions(result[key], value);
  2772. });
  2773. });
  2774. return result;
  2775. }
  2776. /**
  2777. * Player Component - Base class for all UI objects
  2778. *
  2779. * @file component.js
  2780. */
  2781. /**
  2782. * Base class for all UI Components.
  2783. * Components are UI objects which represent both a javascript object and an element
  2784. * in the DOM. They can be children of other components, and can have
  2785. * children themselves.
  2786. *
  2787. * Components can also use methods from {@link EventTarget}
  2788. */
  2789. var Component =
  2790. /*#__PURE__*/
  2791. function () {
  2792. /**
  2793. * A callback that is called when a component is ready. Does not have any
  2794. * paramters and any callback value will be ignored.
  2795. *
  2796. * @callback Component~ReadyCallback
  2797. * @this Component
  2798. */
  2799. /**
  2800. * Creates an instance of this class.
  2801. *
  2802. * @param {Player} player
  2803. * The `Player` that this class should be attached to.
  2804. *
  2805. * @param {Object} [options]
  2806. * The key/value store of player options.
  2807. *
  2808. * @param {Object[]} [options.children]
  2809. * An array of children objects to intialize this component with. Children objects have
  2810. * a name property that will be used if more than one component of the same type needs to be
  2811. * added.
  2812. *
  2813. * @param {Component~ReadyCallback} [ready]
  2814. * Function that gets called when the `Component` is ready.
  2815. */
  2816. function Component(player, options, ready) {
  2817. // The component might be the player itself and we can't pass `this` to super
  2818. if (!player && this.play) {
  2819. this.player_ = player = this; // eslint-disable-line
  2820. } else {
  2821. this.player_ = player;
  2822. } // Hold the reference to the parent component via `addChild` method
  2823. this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
  2824. this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options
  2825. options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied
  2826. this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
  2827. if (!this.id_) {
  2828. // Don't require the player ID function in the case of mock players
  2829. var id = player && player.id && player.id() || 'no_player';
  2830. this.id_ = id + "_component_" + newGUID();
  2831. }
  2832. this.name_ = options.name || null; // Create element if one wasn't provided in options
  2833. if (options.el) {
  2834. this.el_ = options.el;
  2835. } else if (options.createEl !== false) {
  2836. this.el_ = this.createEl();
  2837. } // if evented is anything except false, we want to mixin in evented
  2838. if (options.evented !== false) {
  2839. // Make this an evented object and use `el_`, if available, as its event bus
  2840. evented(this, {
  2841. eventBusKey: this.el_ ? 'el_' : null
  2842. });
  2843. }
  2844. stateful(this, this.constructor.defaultState);
  2845. this.children_ = [];
  2846. this.childIndex_ = {};
  2847. this.childNameIndex_ = {}; // Add any child components in options
  2848. if (options.initChildren !== false) {
  2849. this.initChildren();
  2850. }
  2851. this.ready(ready); // Don't want to trigger ready here or it will before init is actually
  2852. // finished for all children that run this constructor
  2853. if (options.reportTouchActivity !== false) {
  2854. this.enableTouchActivity();
  2855. }
  2856. }
  2857. /**
  2858. * Dispose of the `Component` and all child components.
  2859. *
  2860. * @fires Component#dispose
  2861. */
  2862. var _proto = Component.prototype;
  2863. _proto.dispose = function dispose() {
  2864. /**
  2865. * Triggered when a `Component` is disposed.
  2866. *
  2867. * @event Component#dispose
  2868. * @type {EventTarget~Event}
  2869. *
  2870. * @property {boolean} [bubbles=false]
  2871. * set to false so that the close event does not
  2872. * bubble up
  2873. */
  2874. this.trigger({
  2875. type: 'dispose',
  2876. bubbles: false
  2877. }); // Dispose all children.
  2878. if (this.children_) {
  2879. for (var i = this.children_.length - 1; i >= 0; i--) {
  2880. if (this.children_[i].dispose) {
  2881. this.children_[i].dispose();
  2882. }
  2883. }
  2884. } // Delete child references
  2885. this.children_ = null;
  2886. this.childIndex_ = null;
  2887. this.childNameIndex_ = null;
  2888. this.parentComponent_ = null;
  2889. if (this.el_) {
  2890. // Remove element from DOM
  2891. if (this.el_.parentNode) {
  2892. this.el_.parentNode.removeChild(this.el_);
  2893. }
  2894. removeData(this.el_);
  2895. this.el_ = null;
  2896. } // remove reference to the player after disposing of the element
  2897. this.player_ = null;
  2898. }
  2899. /**
  2900. * Return the {@link Player} that the `Component` has attached to.
  2901. *
  2902. * @return {Player}
  2903. * The player that this `Component` has attached to.
  2904. */
  2905. ;
  2906. _proto.player = function player() {
  2907. return this.player_;
  2908. }
  2909. /**
  2910. * Deep merge of options objects with new options.
  2911. * > Note: When both `obj` and `options` contain properties whose values are objects.
  2912. * The two properties get merged using {@link module:mergeOptions}
  2913. *
  2914. * @param {Object} obj
  2915. * The object that contains new options.
  2916. *
  2917. * @return {Object}
  2918. * A new object of `this.options_` and `obj` merged together.
  2919. */
  2920. ;
  2921. _proto.options = function options(obj) {
  2922. if (!obj) {
  2923. return this.options_;
  2924. }
  2925. this.options_ = mergeOptions(this.options_, obj);
  2926. return this.options_;
  2927. }
  2928. /**
  2929. * Get the `Component`s DOM element
  2930. *
  2931. * @return {Element}
  2932. * The DOM element for this `Component`.
  2933. */
  2934. ;
  2935. _proto.el = function el() {
  2936. return this.el_;
  2937. }
  2938. /**
  2939. * Create the `Component`s DOM element.
  2940. *
  2941. * @param {string} [tagName]
  2942. * Element's DOM node type. e.g. 'div'
  2943. *
  2944. * @param {Object} [properties]
  2945. * An object of properties that should be set.
  2946. *
  2947. * @param {Object} [attributes]
  2948. * An object of attributes that should be set.
  2949. *
  2950. * @return {Element}
  2951. * The element that gets created.
  2952. */
  2953. ;
  2954. _proto.createEl = function createEl$1(tagName, properties, attributes) {
  2955. return createEl(tagName, properties, attributes);
  2956. }
  2957. /**
  2958. * Localize a string given the string in english.
  2959. *
  2960. * If tokens are provided, it'll try and run a simple token replacement on the provided string.
  2961. * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
  2962. *
  2963. * If a `defaultValue` is provided, it'll use that over `string`,
  2964. * if a value isn't found in provided language files.
  2965. * This is useful if you want to have a descriptive key for token replacement
  2966. * but have a succinct localized string and not require `en.json` to be included.
  2967. *
  2968. * Currently, it is used for the progress bar timing.
  2969. * ```js
  2970. * {
  2971. * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
  2972. * }
  2973. * ```
  2974. * It is then used like so:
  2975. * ```js
  2976. * this.localize('progress bar timing: currentTime={1} duration{2}',
  2977. * [this.player_.currentTime(), this.player_.duration()],
  2978. * '{1} of {2}');
  2979. * ```
  2980. *
  2981. * Which outputs something like: `01:23 of 24:56`.
  2982. *
  2983. *
  2984. * @param {string} string
  2985. * The string to localize and the key to lookup in the language files.
  2986. * @param {string[]} [tokens]
  2987. * If the current item has token replacements, provide the tokens here.
  2988. * @param {string} [defaultValue]
  2989. * Defaults to `string`. Can be a default value to use for token replacement
  2990. * if the lookup key is needed to be separate.
  2991. *
  2992. * @return {string}
  2993. * The localized string or if no localization exists the english string.
  2994. */
  2995. ;
  2996. _proto.localize = function localize(string, tokens, defaultValue) {
  2997. if (defaultValue === void 0) {
  2998. defaultValue = string;
  2999. }
  3000. var code = this.player_.language && this.player_.language();
  3001. var languages = this.player_.languages && this.player_.languages();
  3002. var language = languages && languages[code];
  3003. var primaryCode = code && code.split('-')[0];
  3004. var primaryLang = languages && languages[primaryCode];
  3005. var localizedString = defaultValue;
  3006. if (language && language[string]) {
  3007. localizedString = language[string];
  3008. } else if (primaryLang && primaryLang[string]) {
  3009. localizedString = primaryLang[string];
  3010. }
  3011. if (tokens) {
  3012. localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
  3013. var value = tokens[index - 1];
  3014. var ret = value;
  3015. if (typeof value === 'undefined') {
  3016. ret = match;
  3017. }
  3018. return ret;
  3019. });
  3020. }
  3021. return localizedString;
  3022. }
  3023. /**
  3024. * Return the `Component`s DOM element. This is where children get inserted.
  3025. * This will usually be the the same as the element returned in {@link Component#el}.
  3026. *
  3027. * @return {Element}
  3028. * The content element for this `Component`.
  3029. */
  3030. ;
  3031. _proto.contentEl = function contentEl() {
  3032. return this.contentEl_ || this.el_;
  3033. }
  3034. /**
  3035. * Get this `Component`s ID
  3036. *
  3037. * @return {string}
  3038. * The id of this `Component`
  3039. */
  3040. ;
  3041. _proto.id = function id() {
  3042. return this.id_;
  3043. }
  3044. /**
  3045. * Get the `Component`s name. The name gets used to reference the `Component`
  3046. * and is set during registration.
  3047. *
  3048. * @return {string}
  3049. * The name of this `Component`.
  3050. */
  3051. ;
  3052. _proto.name = function name() {
  3053. return this.name_;
  3054. }
  3055. /**
  3056. * Get an array of all child components
  3057. *
  3058. * @return {Array}
  3059. * The children
  3060. */
  3061. ;
  3062. _proto.children = function children() {
  3063. return this.children_;
  3064. }
  3065. /**
  3066. * Returns the child `Component` with the given `id`.
  3067. *
  3068. * @param {string} id
  3069. * The id of the child `Component` to get.
  3070. *
  3071. * @return {Component|undefined}
  3072. * The child `Component` with the given `id` or undefined.
  3073. */
  3074. ;
  3075. _proto.getChildById = function getChildById(id) {
  3076. return this.childIndex_[id];
  3077. }
  3078. /**
  3079. * Returns the child `Component` with the given `name`.
  3080. *
  3081. * @param {string} name
  3082. * The name of the child `Component` to get.
  3083. *
  3084. * @return {Component|undefined}
  3085. * The child `Component` with the given `name` or undefined.
  3086. */
  3087. ;
  3088. _proto.getChild = function getChild(name) {
  3089. if (!name) {
  3090. return;
  3091. }
  3092. name = toTitleCase(name);
  3093. return this.childNameIndex_[name];
  3094. }
  3095. /**
  3096. * Add a child `Component` inside the current `Component`.
  3097. *
  3098. *
  3099. * @param {string|Component} child
  3100. * The name or instance of a child to add.
  3101. *
  3102. * @param {Object} [options={}]
  3103. * The key/value store of options that will get passed to children of
  3104. * the child.
  3105. *
  3106. * @param {number} [index=this.children_.length]
  3107. * The index to attempt to add a child into.
  3108. *
  3109. * @return {Component}
  3110. * The `Component` that gets added as a child. When using a string the
  3111. * `Component` will get created by this process.
  3112. */
  3113. ;
  3114. _proto.addChild = function addChild(child, options, index) {
  3115. if (options === void 0) {
  3116. options = {};
  3117. }
  3118. if (index === void 0) {
  3119. index = this.children_.length;
  3120. }
  3121. var component;
  3122. var componentName; // If child is a string, create component with options
  3123. if (typeof child === 'string') {
  3124. componentName = toTitleCase(child);
  3125. var componentClassName = options.componentClass || componentName; // Set name through options
  3126. options.name = componentName; // Create a new object & element for this controls set
  3127. // If there's no .player_, this is a player
  3128. var ComponentClass = Component.getComponent(componentClassName);
  3129. if (!ComponentClass) {
  3130. throw new Error("Component " + componentClassName + " does not exist");
  3131. } // data stored directly on the videojs object may be
  3132. // misidentified as a component to retain
  3133. // backwards-compatibility with 4.x. check to make sure the
  3134. // component class can be instantiated.
  3135. if (typeof ComponentClass !== 'function') {
  3136. return null;
  3137. }
  3138. component = new ComponentClass(this.player_ || this, options); // child is a component instance
  3139. } else {
  3140. component = child;
  3141. }
  3142. if (component.parentComponent_) {
  3143. component.parentComponent_.removeChild(component);
  3144. }
  3145. this.children_.splice(index, 0, component);
  3146. component.parentComponent_ = this;
  3147. if (typeof component.id === 'function') {
  3148. this.childIndex_[component.id()] = component;
  3149. } // If a name wasn't used to create the component, check if we can use the
  3150. // name function of the component
  3151. componentName = componentName || component.name && toTitleCase(component.name());
  3152. if (componentName) {
  3153. this.childNameIndex_[componentName] = component;
  3154. } // Add the UI object's element to the container div (box)
  3155. // Having an element is not required
  3156. if (typeof component.el === 'function' && component.el()) {
  3157. var childNodes = this.contentEl().children;
  3158. var refNode = childNodes[index] || null;
  3159. this.contentEl().insertBefore(component.el(), refNode);
  3160. } // Return so it can stored on parent object if desired.
  3161. return component;
  3162. }
  3163. /**
  3164. * Remove a child `Component` from this `Component`s list of children. Also removes
  3165. * the child `Component`s element from this `Component`s element.
  3166. *
  3167. * @param {Component} component
  3168. * The child `Component` to remove.
  3169. */
  3170. ;
  3171. _proto.removeChild = function removeChild(component) {
  3172. if (typeof component === 'string') {
  3173. component = this.getChild(component);
  3174. }
  3175. if (!component || !this.children_) {
  3176. return;
  3177. }
  3178. var childFound = false;
  3179. for (var i = this.children_.length - 1; i >= 0; i--) {
  3180. if (this.children_[i] === component) {
  3181. childFound = true;
  3182. this.children_.splice(i, 1);
  3183. break;
  3184. }
  3185. }
  3186. if (!childFound) {
  3187. return;
  3188. }
  3189. component.parentComponent_ = null;
  3190. this.childIndex_[component.id()] = null;
  3191. this.childNameIndex_[component.name()] = null;
  3192. var compEl = component.el();
  3193. if (compEl && compEl.parentNode === this.contentEl()) {
  3194. this.contentEl().removeChild(component.el());
  3195. }
  3196. }
  3197. /**
  3198. * Add and initialize default child `Component`s based upon options.
  3199. */
  3200. ;
  3201. _proto.initChildren = function initChildren() {
  3202. var _this = this;
  3203. var children = this.options_.children;
  3204. if (children) {
  3205. // `this` is `parent`
  3206. var parentOptions = this.options_;
  3207. var handleAdd = function handleAdd(child) {
  3208. var name = child.name;
  3209. var opts = child.opts; // Allow options for children to be set at the parent options
  3210. // e.g. videojs(id, { controlBar: false });
  3211. // instead of videojs(id, { children: { controlBar: false });
  3212. if (parentOptions[name] !== undefined) {
  3213. opts = parentOptions[name];
  3214. } // Allow for disabling default components
  3215. // e.g. options['children']['posterImage'] = false
  3216. if (opts === false) {
  3217. return;
  3218. } // Allow options to be passed as a simple boolean if no configuration
  3219. // is necessary.
  3220. if (opts === true) {
  3221. opts = {};
  3222. } // We also want to pass the original player options
  3223. // to each component as well so they don't need to
  3224. // reach back into the player for options later.
  3225. opts.playerOptions = _this.options_.playerOptions; // Create and add the child component.
  3226. // Add a direct reference to the child by name on the parent instance.
  3227. // If two of the same component are used, different names should be supplied
  3228. // for each
  3229. var newChild = _this.addChild(name, opts);
  3230. if (newChild) {
  3231. _this[name] = newChild;
  3232. }
  3233. }; // Allow for an array of children details to passed in the options
  3234. var workingChildren;
  3235. var Tech = Component.getComponent('Tech');
  3236. if (Array.isArray(children)) {
  3237. workingChildren = children;
  3238. } else {
  3239. workingChildren = Object.keys(children);
  3240. }
  3241. workingChildren // children that are in this.options_ but also in workingChildren would
  3242. // give us extra children we do not want. So, we want to filter them out.
  3243. .concat(Object.keys(this.options_).filter(function (child) {
  3244. return !workingChildren.some(function (wchild) {
  3245. if (typeof wchild === 'string') {
  3246. return child === wchild;
  3247. }
  3248. return child === wchild.name;
  3249. });
  3250. })).map(function (child) {
  3251. var name;
  3252. var opts;
  3253. if (typeof child === 'string') {
  3254. name = child;
  3255. opts = children[name] || _this.options_[name] || {};
  3256. } else {
  3257. name = child.name;
  3258. opts = child;
  3259. }
  3260. return {
  3261. name: name,
  3262. opts: opts
  3263. };
  3264. }).filter(function (child) {
  3265. // we have to make sure that child.name isn't in the techOrder since
  3266. // techs are registerd as Components but can't aren't compatible
  3267. // See https://github.com/videojs/video.js/issues/2772
  3268. var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
  3269. return c && !Tech.isTech(c);
  3270. }).forEach(handleAdd);
  3271. }
  3272. }
  3273. /**
  3274. * Builds the default DOM class name. Should be overriden by sub-components.
  3275. *
  3276. * @return {string}
  3277. * The DOM class name for this object.
  3278. *
  3279. * @abstract
  3280. */
  3281. ;
  3282. _proto.buildCSSClass = function buildCSSClass() {
  3283. // Child classes can include a function that does:
  3284. // return 'CLASS NAME' + this._super();
  3285. return '';
  3286. }
  3287. /**
  3288. * Bind a listener to the component's ready state.
  3289. * Different from event listeners in that if the ready event has already happened
  3290. * it will trigger the function immediately.
  3291. *
  3292. * @return {Component}
  3293. * Returns itself; method can be chained.
  3294. */
  3295. ;
  3296. _proto.ready = function ready(fn, sync) {
  3297. if (sync === void 0) {
  3298. sync = false;
  3299. }
  3300. if (!fn) {
  3301. return;
  3302. }
  3303. if (!this.isReady_) {
  3304. this.readyQueue_ = this.readyQueue_ || [];
  3305. this.readyQueue_.push(fn);
  3306. return;
  3307. }
  3308. if (sync) {
  3309. fn.call(this);
  3310. } else {
  3311. // Call the function asynchronously by default for consistency
  3312. this.setTimeout(fn, 1);
  3313. }
  3314. }
  3315. /**
  3316. * Trigger all the ready listeners for this `Component`.
  3317. *
  3318. * @fires Component#ready
  3319. */
  3320. ;
  3321. _proto.triggerReady = function triggerReady() {
  3322. this.isReady_ = true; // Ensure ready is triggered asynchronously
  3323. this.setTimeout(function () {
  3324. var readyQueue = this.readyQueue_; // Reset Ready Queue
  3325. this.readyQueue_ = [];
  3326. if (readyQueue && readyQueue.length > 0) {
  3327. readyQueue.forEach(function (fn) {
  3328. fn.call(this);
  3329. }, this);
  3330. } // Allow for using event listeners also
  3331. /**
  3332. * Triggered when a `Component` is ready.
  3333. *
  3334. * @event Component#ready
  3335. * @type {EventTarget~Event}
  3336. */
  3337. this.trigger('ready');
  3338. }, 1);
  3339. }
  3340. /**
  3341. * Find a single DOM element matching a `selector`. This can be within the `Component`s
  3342. * `contentEl()` or another custom context.
  3343. *
  3344. * @param {string} selector
  3345. * A valid CSS selector, which will be passed to `querySelector`.
  3346. *
  3347. * @param {Element|string} [context=this.contentEl()]
  3348. * A DOM element within which to query. Can also be a selector string in
  3349. * which case the first matching element will get used as context. If
  3350. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3351. * nothing it falls back to `document`.
  3352. *
  3353. * @return {Element|null}
  3354. * the dom element that was found, or null
  3355. *
  3356. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3357. */
  3358. ;
  3359. _proto.$ = function $$1(selector, context) {
  3360. return $(selector, context || this.contentEl());
  3361. }
  3362. /**
  3363. * Finds all DOM element matching a `selector`. This can be within the `Component`s
  3364. * `contentEl()` or another custom context.
  3365. *
  3366. * @param {string} selector
  3367. * A valid CSS selector, which will be passed to `querySelectorAll`.
  3368. *
  3369. * @param {Element|string} [context=this.contentEl()]
  3370. * A DOM element within which to query. Can also be a selector string in
  3371. * which case the first matching element will get used as context. If
  3372. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3373. * nothing it falls back to `document`.
  3374. *
  3375. * @return {NodeList}
  3376. * a list of dom elements that were found
  3377. *
  3378. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3379. */
  3380. ;
  3381. _proto.$$ = function $$$1(selector, context) {
  3382. return $$(selector, context || this.contentEl());
  3383. }
  3384. /**
  3385. * Check if a component's element has a CSS class name.
  3386. *
  3387. * @param {string} classToCheck
  3388. * CSS class name to check.
  3389. *
  3390. * @return {boolean}
  3391. * - True if the `Component` has the class.
  3392. * - False if the `Component` does not have the class`
  3393. */
  3394. ;
  3395. _proto.hasClass = function hasClass$1(classToCheck) {
  3396. return hasClass(this.el_, classToCheck);
  3397. }
  3398. /**
  3399. * Add a CSS class name to the `Component`s element.
  3400. *
  3401. * @param {string} classToAdd
  3402. * CSS class name to add
  3403. */
  3404. ;
  3405. _proto.addClass = function addClass$1(classToAdd) {
  3406. addClass(this.el_, classToAdd);
  3407. }
  3408. /**
  3409. * Remove a CSS class name from the `Component`s element.
  3410. *
  3411. * @param {string} classToRemove
  3412. * CSS class name to remove
  3413. */
  3414. ;
  3415. _proto.removeClass = function removeClass$1(classToRemove) {
  3416. removeClass(this.el_, classToRemove);
  3417. }
  3418. /**
  3419. * Add or remove a CSS class name from the component's element.
  3420. * - `classToToggle` gets added when {@link Component#hasClass} would return false.
  3421. * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
  3422. *
  3423. * @param {string} classToToggle
  3424. * The class to add or remove based on (@link Component#hasClass}
  3425. *
  3426. * @param {boolean|Dom~predicate} [predicate]
  3427. * An {@link Dom~predicate} function or a boolean
  3428. */
  3429. ;
  3430. _proto.toggleClass = function toggleClass$1(classToToggle, predicate) {
  3431. toggleClass(this.el_, classToToggle, predicate);
  3432. }
  3433. /**
  3434. * Show the `Component`s element if it is hidden by removing the
  3435. * 'vjs-hidden' class name from it.
  3436. */
  3437. ;
  3438. _proto.show = function show() {
  3439. this.removeClass('vjs-hidden');
  3440. }
  3441. /**
  3442. * Hide the `Component`s element if it is currently showing by adding the
  3443. * 'vjs-hidden` class name to it.
  3444. */
  3445. ;
  3446. _proto.hide = function hide() {
  3447. this.addClass('vjs-hidden');
  3448. }
  3449. /**
  3450. * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
  3451. * class name to it. Used during fadeIn/fadeOut.
  3452. *
  3453. * @private
  3454. */
  3455. ;
  3456. _proto.lockShowing = function lockShowing() {
  3457. this.addClass('vjs-lock-showing');
  3458. }
  3459. /**
  3460. * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
  3461. * class name from it. Used during fadeIn/fadeOut.
  3462. *
  3463. * @private
  3464. */
  3465. ;
  3466. _proto.unlockShowing = function unlockShowing() {
  3467. this.removeClass('vjs-lock-showing');
  3468. }
  3469. /**
  3470. * Get the value of an attribute on the `Component`s element.
  3471. *
  3472. * @param {string} attribute
  3473. * Name of the attribute to get the value from.
  3474. *
  3475. * @return {string|null}
  3476. * - The value of the attribute that was asked for.
  3477. * - Can be an empty string on some browsers if the attribute does not exist
  3478. * or has no value
  3479. * - Most browsers will return null if the attibute does not exist or has
  3480. * no value.
  3481. *
  3482. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
  3483. */
  3484. ;
  3485. _proto.getAttribute = function getAttribute$1(attribute) {
  3486. return getAttribute(this.el_, attribute);
  3487. }
  3488. /**
  3489. * Set the value of an attribute on the `Component`'s element
  3490. *
  3491. * @param {string} attribute
  3492. * Name of the attribute to set.
  3493. *
  3494. * @param {string} value
  3495. * Value to set the attribute to.
  3496. *
  3497. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
  3498. */
  3499. ;
  3500. _proto.setAttribute = function setAttribute$1(attribute, value) {
  3501. setAttribute(this.el_, attribute, value);
  3502. }
  3503. /**
  3504. * Remove an attribute from the `Component`s element.
  3505. *
  3506. * @param {string} attribute
  3507. * Name of the attribute to remove.
  3508. *
  3509. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
  3510. */
  3511. ;
  3512. _proto.removeAttribute = function removeAttribute$1(attribute) {
  3513. removeAttribute(this.el_, attribute);
  3514. }
  3515. /**
  3516. * Get or set the width of the component based upon the CSS styles.
  3517. * See {@link Component#dimension} for more detailed information.
  3518. *
  3519. * @param {number|string} [num]
  3520. * The width that you want to set postfixed with '%', 'px' or nothing.
  3521. *
  3522. * @param {boolean} [skipListeners]
  3523. * Skip the componentresize event trigger
  3524. *
  3525. * @return {number|string}
  3526. * The width when getting, zero if there is no width. Can be a string
  3527. * postpixed with '%' or 'px'.
  3528. */
  3529. ;
  3530. _proto.width = function width(num, skipListeners) {
  3531. return this.dimension('width', num, skipListeners);
  3532. }
  3533. /**
  3534. * Get or set the height of the component based upon the CSS styles.
  3535. * See {@link Component#dimension} for more detailed information.
  3536. *
  3537. * @param {number|string} [num]
  3538. * The height that you want to set postfixed with '%', 'px' or nothing.
  3539. *
  3540. * @param {boolean} [skipListeners]
  3541. * Skip the componentresize event trigger
  3542. *
  3543. * @return {number|string}
  3544. * The width when getting, zero if there is no width. Can be a string
  3545. * postpixed with '%' or 'px'.
  3546. */
  3547. ;
  3548. _proto.height = function height(num, skipListeners) {
  3549. return this.dimension('height', num, skipListeners);
  3550. }
  3551. /**
  3552. * Set both the width and height of the `Component` element at the same time.
  3553. *
  3554. * @param {number|string} width
  3555. * Width to set the `Component`s element to.
  3556. *
  3557. * @param {number|string} height
  3558. * Height to set the `Component`s element to.
  3559. */
  3560. ;
  3561. _proto.dimensions = function dimensions(width, height) {
  3562. // Skip componentresize listeners on width for optimization
  3563. this.width(width, true);
  3564. this.height(height);
  3565. }
  3566. /**
  3567. * Get or set width or height of the `Component` element. This is the shared code
  3568. * for the {@link Component#width} and {@link Component#height}.
  3569. *
  3570. * Things to know:
  3571. * - If the width or height in an number this will return the number postfixed with 'px'.
  3572. * - If the width/height is a percent this will return the percent postfixed with '%'
  3573. * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
  3574. * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
  3575. * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
  3576. * for more information
  3577. * - If you want the computed style of the component, use {@link Component#currentWidth}
  3578. * and {@link {Component#currentHeight}
  3579. *
  3580. * @fires Component#componentresize
  3581. *
  3582. * @param {string} widthOrHeight
  3583. 8 'width' or 'height'
  3584. *
  3585. * @param {number|string} [num]
  3586. 8 New dimension
  3587. *
  3588. * @param {boolean} [skipListeners]
  3589. * Skip componentresize event trigger
  3590. *
  3591. * @return {number}
  3592. * The dimension when getting or 0 if unset
  3593. */
  3594. ;
  3595. _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
  3596. if (num !== undefined) {
  3597. // Set to zero if null or literally NaN (NaN !== NaN)
  3598. if (num === null || num !== num) {
  3599. num = 0;
  3600. } // Check if using css width/height (% or px) and adjust
  3601. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  3602. this.el_.style[widthOrHeight] = num;
  3603. } else if (num === 'auto') {
  3604. this.el_.style[widthOrHeight] = '';
  3605. } else {
  3606. this.el_.style[widthOrHeight] = num + 'px';
  3607. } // skipListeners allows us to avoid triggering the resize event when setting both width and height
  3608. if (!skipListeners) {
  3609. /**
  3610. * Triggered when a component is resized.
  3611. *
  3612. * @event Component#componentresize
  3613. * @type {EventTarget~Event}
  3614. */
  3615. this.trigger('componentresize');
  3616. }
  3617. return;
  3618. } // Not setting a value, so getting it
  3619. // Make sure element exists
  3620. if (!this.el_) {
  3621. return 0;
  3622. } // Get dimension value from style
  3623. var val = this.el_.style[widthOrHeight];
  3624. var pxIndex = val.indexOf('px');
  3625. if (pxIndex !== -1) {
  3626. // Return the pixel value with no 'px'
  3627. return parseInt(val.slice(0, pxIndex), 10);
  3628. } // No px so using % or no style was set, so falling back to offsetWidth/height
  3629. // If component has display:none, offset will return 0
  3630. // TODO: handle display:none and no dimension style using px
  3631. return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
  3632. }
  3633. /**
  3634. * Get the computed width or the height of the component's element.
  3635. *
  3636. * Uses `window.getComputedStyle`.
  3637. *
  3638. * @param {string} widthOrHeight
  3639. * A string containing 'width' or 'height'. Whichever one you want to get.
  3640. *
  3641. * @return {number}
  3642. * The dimension that gets asked for or 0 if nothing was set
  3643. * for that dimension.
  3644. */
  3645. ;
  3646. _proto.currentDimension = function currentDimension(widthOrHeight) {
  3647. var computedWidthOrHeight = 0;
  3648. if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
  3649. throw new Error('currentDimension only accepts width or height value');
  3650. }
  3651. if (typeof window$1.getComputedStyle === 'function') {
  3652. var computedStyle = window$1.getComputedStyle(this.el_);
  3653. computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
  3654. } // remove 'px' from variable and parse as integer
  3655. computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
  3656. // and we want to check the offset values.
  3657. // This code also runs wherever getComputedStyle doesn't exist.
  3658. if (computedWidthOrHeight === 0) {
  3659. var rule = "offset" + toTitleCase(widthOrHeight);
  3660. computedWidthOrHeight = this.el_[rule];
  3661. }
  3662. return computedWidthOrHeight;
  3663. }
  3664. /**
  3665. * An object that contains width and height values of the `Component`s
  3666. * computed style. Uses `window.getComputedStyle`.
  3667. *
  3668. * @typedef {Object} Component~DimensionObject
  3669. *
  3670. * @property {number} width
  3671. * The width of the `Component`s computed style.
  3672. *
  3673. * @property {number} height
  3674. * The height of the `Component`s computed style.
  3675. */
  3676. /**
  3677. * Get an object that contains computed width and height values of the
  3678. * component's element.
  3679. *
  3680. * Uses `window.getComputedStyle`.
  3681. *
  3682. * @return {Component~DimensionObject}
  3683. * The computed dimensions of the component's element.
  3684. */
  3685. ;
  3686. _proto.currentDimensions = function currentDimensions() {
  3687. return {
  3688. width: this.currentDimension('width'),
  3689. height: this.currentDimension('height')
  3690. };
  3691. }
  3692. /**
  3693. * Get the computed width of the component's element.
  3694. *
  3695. * Uses `window.getComputedStyle`.
  3696. *
  3697. * @return {number}
  3698. * The computed width of the component's element.
  3699. */
  3700. ;
  3701. _proto.currentWidth = function currentWidth() {
  3702. return this.currentDimension('width');
  3703. }
  3704. /**
  3705. * Get the computed height of the component's element.
  3706. *
  3707. * Uses `window.getComputedStyle`.
  3708. *
  3709. * @return {number}
  3710. * The computed height of the component's element.
  3711. */
  3712. ;
  3713. _proto.currentHeight = function currentHeight() {
  3714. return this.currentDimension('height');
  3715. }
  3716. /**
  3717. * Set the focus to this component
  3718. */
  3719. ;
  3720. _proto.focus = function focus() {
  3721. this.el_.focus();
  3722. }
  3723. /**
  3724. * Remove the focus from this component
  3725. */
  3726. ;
  3727. _proto.blur = function blur() {
  3728. this.el_.blur();
  3729. }
  3730. /**
  3731. * When this Component receives a `keydown` event which it does not process,
  3732. * it passes the event to the Player for handling.
  3733. *
  3734. * @param {EventTarget~Event} event
  3735. * The `keydown` event that caused this function to be called.
  3736. */
  3737. ;
  3738. _proto.handleKeyDown = function handleKeyDown(event) {
  3739. if (this.player_) {
  3740. // We only stop propagation here because we want unhandled events to fall
  3741. // back to the browser.
  3742. event.stopPropagation();
  3743. this.player_.handleKeyDown(event);
  3744. }
  3745. }
  3746. /**
  3747. * Many components used to have a `handleKeyPress` method, which was poorly
  3748. * named because it listened to a `keydown` event. This method name now
  3749. * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
  3750. * will not see their method calls stop working.
  3751. *
  3752. * @param {EventTarget~Event} event
  3753. * The event that caused this function to be called.
  3754. */
  3755. ;
  3756. _proto.handleKeyPress = function handleKeyPress(event) {
  3757. this.handleKeyDown(event);
  3758. }
  3759. /**
  3760. * Emit a 'tap' events when touch event support gets detected. This gets used to
  3761. * support toggling the controls through a tap on the video. They get enabled
  3762. * because every sub-component would have extra overhead otherwise.
  3763. *
  3764. * @private
  3765. * @fires Component#tap
  3766. * @listens Component#touchstart
  3767. * @listens Component#touchmove
  3768. * @listens Component#touchleave
  3769. * @listens Component#touchcancel
  3770. * @listens Component#touchend
  3771. */
  3772. ;
  3773. _proto.emitTapEvents = function emitTapEvents() {
  3774. // Track the start time so we can determine how long the touch lasted
  3775. var touchStart = 0;
  3776. var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
  3777. // Other popular libs use anywhere from 2 (hammer.js) to 15,
  3778. // so 10 seems like a nice, round number.
  3779. var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
  3780. var touchTimeThreshold = 200;
  3781. var couldBeTap;
  3782. this.on('touchstart', function (event) {
  3783. // If more than one finger, don't consider treating this as a click
  3784. if (event.touches.length === 1) {
  3785. // Copy pageX/pageY from the object
  3786. firstTouch = {
  3787. pageX: event.touches[0].pageX,
  3788. pageY: event.touches[0].pageY
  3789. }; // Record start time so we can detect a tap vs. "touch and hold"
  3790. touchStart = window$1.performance.now(); // Reset couldBeTap tracking
  3791. couldBeTap = true;
  3792. }
  3793. });
  3794. this.on('touchmove', function (event) {
  3795. // If more than one finger, don't consider treating this as a click
  3796. if (event.touches.length > 1) {
  3797. couldBeTap = false;
  3798. } else if (firstTouch) {
  3799. // Some devices will throw touchmoves for all but the slightest of taps.
  3800. // So, if we moved only a small distance, this could still be a tap
  3801. var xdiff = event.touches[0].pageX - firstTouch.pageX;
  3802. var ydiff = event.touches[0].pageY - firstTouch.pageY;
  3803. var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
  3804. if (touchDistance > tapMovementThreshold) {
  3805. couldBeTap = false;
  3806. }
  3807. }
  3808. });
  3809. var noTap = function noTap() {
  3810. couldBeTap = false;
  3811. }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  3812. this.on('touchleave', noTap);
  3813. this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
  3814. // event
  3815. this.on('touchend', function (event) {
  3816. firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
  3817. if (couldBeTap === true) {
  3818. // Measure how long the touch lasted
  3819. var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
  3820. if (touchTime < touchTimeThreshold) {
  3821. // Don't let browser turn this into a click
  3822. event.preventDefault();
  3823. /**
  3824. * Triggered when a `Component` is tapped.
  3825. *
  3826. * @event Component#tap
  3827. * @type {EventTarget~Event}
  3828. */
  3829. this.trigger('tap'); // It may be good to copy the touchend event object and change the
  3830. // type to tap, if the other event properties aren't exact after
  3831. // Events.fixEvent runs (e.g. event.target)
  3832. }
  3833. }
  3834. });
  3835. }
  3836. /**
  3837. * This function reports user activity whenever touch events happen. This can get
  3838. * turned off by any sub-components that wants touch events to act another way.
  3839. *
  3840. * Report user touch activity when touch events occur. User activity gets used to
  3841. * determine when controls should show/hide. It is simple when it comes to mouse
  3842. * events, because any mouse event should show the controls. So we capture mouse
  3843. * events that bubble up to the player and report activity when that happens.
  3844. * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
  3845. * controls. So touch events can't help us at the player level either.
  3846. *
  3847. * User activity gets checked asynchronously. So what could happen is a tap event
  3848. * on the video turns the controls off. Then the `touchend` event bubbles up to
  3849. * the player. Which, if it reported user activity, would turn the controls right
  3850. * back on. We also don't want to completely block touch events from bubbling up.
  3851. * Furthermore a `touchmove` event and anything other than a tap, should not turn
  3852. * controls back on.
  3853. *
  3854. * @listens Component#touchstart
  3855. * @listens Component#touchmove
  3856. * @listens Component#touchend
  3857. * @listens Component#touchcancel
  3858. */
  3859. ;
  3860. _proto.enableTouchActivity = function enableTouchActivity() {
  3861. // Don't continue if the root player doesn't support reporting user activity
  3862. if (!this.player() || !this.player().reportUserActivity) {
  3863. return;
  3864. } // listener for reporting that the user is active
  3865. var report = bind(this.player(), this.player().reportUserActivity);
  3866. var touchHolding;
  3867. this.on('touchstart', function () {
  3868. report(); // For as long as the they are touching the device or have their mouse down,
  3869. // we consider them active even if they're not moving their finger or mouse.
  3870. // So we want to continue to update that they are active
  3871. this.clearInterval(touchHolding); // report at the same interval as activityCheck
  3872. touchHolding = this.setInterval(report, 250);
  3873. });
  3874. var touchEnd = function touchEnd(event) {
  3875. report(); // stop the interval that maintains activity if the touch is holding
  3876. this.clearInterval(touchHolding);
  3877. };
  3878. this.on('touchmove', report);
  3879. this.on('touchend', touchEnd);
  3880. this.on('touchcancel', touchEnd);
  3881. }
  3882. /**
  3883. * A callback that has no parameters and is bound into `Component`s context.
  3884. *
  3885. * @callback Component~GenericCallback
  3886. * @this Component
  3887. */
  3888. /**
  3889. * Creates a function that runs after an `x` millisecond timeout. This function is a
  3890. * wrapper around `window.setTimeout`. There are a few reasons to use this one
  3891. * instead though:
  3892. * 1. It gets cleared via {@link Component#clearTimeout} when
  3893. * {@link Component#dispose} gets called.
  3894. * 2. The function callback will gets turned into a {@link Component~GenericCallback}
  3895. *
  3896. * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
  3897. * will cause its dispose listener not to get cleaned up! Please use
  3898. * {@link Component#clearTimeout} or {@link Component#dispose} instead.
  3899. *
  3900. * @param {Component~GenericCallback} fn
  3901. * The function that will be run after `timeout`.
  3902. *
  3903. * @param {number} timeout
  3904. * Timeout in milliseconds to delay before executing the specified function.
  3905. *
  3906. * @return {number}
  3907. * Returns a timeout ID that gets used to identify the timeout. It can also
  3908. * get used in {@link Component#clearTimeout} to clear the timeout that
  3909. * was set.
  3910. *
  3911. * @listens Component#dispose
  3912. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
  3913. */
  3914. ;
  3915. _proto.setTimeout = function setTimeout(fn, timeout) {
  3916. var _this2 = this;
  3917. // declare as variables so they are properly available in timeout function
  3918. // eslint-disable-next-line
  3919. var timeoutId, disposeFn;
  3920. fn = bind(this, fn);
  3921. timeoutId = window$1.setTimeout(function () {
  3922. _this2.off('dispose', disposeFn);
  3923. fn();
  3924. }, timeout);
  3925. disposeFn = function disposeFn() {
  3926. return _this2.clearTimeout(timeoutId);
  3927. };
  3928. disposeFn.guid = "vjs-timeout-" + timeoutId;
  3929. this.on('dispose', disposeFn);
  3930. return timeoutId;
  3931. }
  3932. /**
  3933. * Clears a timeout that gets created via `window.setTimeout` or
  3934. * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
  3935. * use this function instead of `window.clearTimout`. If you don't your dispose
  3936. * listener will not get cleaned up until {@link Component#dispose}!
  3937. *
  3938. * @param {number} timeoutId
  3939. * The id of the timeout to clear. The return value of
  3940. * {@link Component#setTimeout} or `window.setTimeout`.
  3941. *
  3942. * @return {number}
  3943. * Returns the timeout id that was cleared.
  3944. *
  3945. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
  3946. */
  3947. ;
  3948. _proto.clearTimeout = function clearTimeout(timeoutId) {
  3949. window$1.clearTimeout(timeoutId);
  3950. var disposeFn = function disposeFn() {};
  3951. disposeFn.guid = "vjs-timeout-" + timeoutId;
  3952. this.off('dispose', disposeFn);
  3953. return timeoutId;
  3954. }
  3955. /**
  3956. * Creates a function that gets run every `x` milliseconds. This function is a wrapper
  3957. * around `window.setInterval`. There are a few reasons to use this one instead though.
  3958. * 1. It gets cleared via {@link Component#clearInterval} when
  3959. * {@link Component#dispose} gets called.
  3960. * 2. The function callback will be a {@link Component~GenericCallback}
  3961. *
  3962. * @param {Component~GenericCallback} fn
  3963. * The function to run every `x` seconds.
  3964. *
  3965. * @param {number} interval
  3966. * Execute the specified function every `x` milliseconds.
  3967. *
  3968. * @return {number}
  3969. * Returns an id that can be used to identify the interval. It can also be be used in
  3970. * {@link Component#clearInterval} to clear the interval.
  3971. *
  3972. * @listens Component#dispose
  3973. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
  3974. */
  3975. ;
  3976. _proto.setInterval = function setInterval(fn, interval) {
  3977. var _this3 = this;
  3978. fn = bind(this, fn);
  3979. var intervalId = window$1.setInterval(fn, interval);
  3980. var disposeFn = function disposeFn() {
  3981. return _this3.clearInterval(intervalId);
  3982. };
  3983. disposeFn.guid = "vjs-interval-" + intervalId;
  3984. this.on('dispose', disposeFn);
  3985. return intervalId;
  3986. }
  3987. /**
  3988. * Clears an interval that gets created via `window.setInterval` or
  3989. * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
  3990. * use this function instead of `window.clearInterval`. If you don't your dispose
  3991. * listener will not get cleaned up until {@link Component#dispose}!
  3992. *
  3993. * @param {number} intervalId
  3994. * The id of the interval to clear. The return value of
  3995. * {@link Component#setInterval} or `window.setInterval`.
  3996. *
  3997. * @return {number}
  3998. * Returns the interval id that was cleared.
  3999. *
  4000. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
  4001. */
  4002. ;
  4003. _proto.clearInterval = function clearInterval(intervalId) {
  4004. window$1.clearInterval(intervalId);
  4005. var disposeFn = function disposeFn() {};
  4006. disposeFn.guid = "vjs-interval-" + intervalId;
  4007. this.off('dispose', disposeFn);
  4008. return intervalId;
  4009. }
  4010. /**
  4011. * Queues up a callback to be passed to requestAnimationFrame (rAF), but
  4012. * with a few extra bonuses:
  4013. *
  4014. * - Supports browsers that do not support rAF by falling back to
  4015. * {@link Component#setTimeout}.
  4016. *
  4017. * - The callback is turned into a {@link Component~GenericCallback} (i.e.
  4018. * bound to the component).
  4019. *
  4020. * - Automatic cancellation of the rAF callback is handled if the component
  4021. * is disposed before it is called.
  4022. *
  4023. * @param {Component~GenericCallback} fn
  4024. * A function that will be bound to this component and executed just
  4025. * before the browser's next repaint.
  4026. *
  4027. * @return {number}
  4028. * Returns an rAF ID that gets used to identify the timeout. It can
  4029. * also be used in {@link Component#cancelAnimationFrame} to cancel
  4030. * the animation frame callback.
  4031. *
  4032. * @listens Component#dispose
  4033. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
  4034. */
  4035. ;
  4036. _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
  4037. var _this4 = this;
  4038. // declare as variables so they are properly available in rAF function
  4039. // eslint-disable-next-line
  4040. var id, disposeFn;
  4041. if (this.supportsRaf_) {
  4042. fn = bind(this, fn);
  4043. id = window$1.requestAnimationFrame(function () {
  4044. _this4.off('dispose', disposeFn);
  4045. fn();
  4046. });
  4047. disposeFn = function disposeFn() {
  4048. return _this4.cancelAnimationFrame(id);
  4049. };
  4050. disposeFn.guid = "vjs-raf-" + id;
  4051. this.on('dispose', disposeFn);
  4052. return id;
  4053. } // Fall back to using a timer.
  4054. return this.setTimeout(fn, 1000 / 60);
  4055. }
  4056. /**
  4057. * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
  4058. * (rAF).
  4059. *
  4060. * If you queue an rAF callback via {@link Component#requestAnimationFrame},
  4061. * use this function instead of `window.cancelAnimationFrame`. If you don't,
  4062. * your dispose listener will not get cleaned up until {@link Component#dispose}!
  4063. *
  4064. * @param {number} id
  4065. * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
  4066. *
  4067. * @return {number}
  4068. * Returns the rAF ID that was cleared.
  4069. *
  4070. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
  4071. */
  4072. ;
  4073. _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
  4074. if (this.supportsRaf_) {
  4075. window$1.cancelAnimationFrame(id);
  4076. var disposeFn = function disposeFn() {};
  4077. disposeFn.guid = "vjs-raf-" + id;
  4078. this.off('dispose', disposeFn);
  4079. return id;
  4080. } // Fall back to using a timer.
  4081. return this.clearTimeout(id);
  4082. }
  4083. /**
  4084. * Register a `Component` with `videojs` given the name and the component.
  4085. *
  4086. * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
  4087. * should be registered using {@link Tech.registerTech} or
  4088. * {@link videojs:videojs.registerTech}.
  4089. *
  4090. * > NOTE: This function can also be seen on videojs as
  4091. * {@link videojs:videojs.registerComponent}.
  4092. *
  4093. * @param {string} name
  4094. * The name of the `Component` to register.
  4095. *
  4096. * @param {Component} ComponentToRegister
  4097. * The `Component` class to register.
  4098. *
  4099. * @return {Component}
  4100. * The `Component` that was registered.
  4101. */
  4102. ;
  4103. Component.registerComponent = function registerComponent(name, ComponentToRegister) {
  4104. if (typeof name !== 'string' || !name) {
  4105. throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
  4106. }
  4107. var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
  4108. var isTech = Tech && Tech.isTech(ComponentToRegister);
  4109. var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
  4110. if (isTech || !isComp) {
  4111. var reason;
  4112. if (isTech) {
  4113. reason = 'techs must be registered using Tech.registerTech()';
  4114. } else {
  4115. reason = 'must be a Component subclass';
  4116. }
  4117. throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
  4118. }
  4119. name = toTitleCase(name);
  4120. if (!Component.components_) {
  4121. Component.components_ = {};
  4122. }
  4123. var Player = Component.getComponent('Player');
  4124. if (name === 'Player' && Player && Player.players) {
  4125. var players = Player.players;
  4126. var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
  4127. // in Players.players. So, we must loop through and verify that the value
  4128. // for each item is not null. This allows registration of the Player component
  4129. // after all players have been disposed or before any were created.
  4130. if (players && playerNames.length > 0 && playerNames.map(function (pname) {
  4131. return players[pname];
  4132. }).every(Boolean)) {
  4133. throw new Error('Can not register Player component after player has been created.');
  4134. }
  4135. }
  4136. Component.components_[name] = ComponentToRegister;
  4137. return ComponentToRegister;
  4138. }
  4139. /**
  4140. * Get a `Component` based on the name it was registered with.
  4141. *
  4142. * @param {string} name
  4143. * The Name of the component to get.
  4144. *
  4145. * @return {Component}
  4146. * The `Component` that got registered under the given name.
  4147. *
  4148. * @deprecated In `videojs` 6 this will not return `Component`s that were not
  4149. * registered using {@link Component.registerComponent}. Currently we
  4150. * check the global `videojs` object for a `Component` name and
  4151. * return that if it exists.
  4152. */
  4153. ;
  4154. Component.getComponent = function getComponent(name) {
  4155. if (!name) {
  4156. return;
  4157. }
  4158. name = toTitleCase(name);
  4159. if (Component.components_ && Component.components_[name]) {
  4160. return Component.components_[name];
  4161. }
  4162. };
  4163. return Component;
  4164. }();
  4165. /**
  4166. * Whether or not this component supports `requestAnimationFrame`.
  4167. *
  4168. * This is exposed primarily for testing purposes.
  4169. *
  4170. * @private
  4171. * @type {Boolean}
  4172. */
  4173. Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
  4174. Component.registerComponent('Component', Component);
  4175. /**
  4176. * @file browser.js
  4177. * @module browser
  4178. */
  4179. var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
  4180. var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
  4181. var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
  4182. /**
  4183. * Whether or not this device is an iPad.
  4184. *
  4185. * @static
  4186. * @const
  4187. * @type {Boolean}
  4188. */
  4189. var IS_IPAD = /iPad/i.test(USER_AGENT);
  4190. /**
  4191. * Whether or not this device is an iPhone.
  4192. *
  4193. * @static
  4194. * @const
  4195. * @type {Boolean}
  4196. */
  4197. // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
  4198. // to identify iPhones, we need to exclude iPads.
  4199. // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
  4200. var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
  4201. /**
  4202. * Whether or not this device is an iPod.
  4203. *
  4204. * @static
  4205. * @const
  4206. * @type {Boolean}
  4207. */
  4208. var IS_IPOD = /iPod/i.test(USER_AGENT);
  4209. /**
  4210. * Whether or not this is an iOS device.
  4211. *
  4212. * @static
  4213. * @const
  4214. * @type {Boolean}
  4215. */
  4216. var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
  4217. /**
  4218. * The detected iOS version - or `null`.
  4219. *
  4220. * @static
  4221. * @const
  4222. * @type {string|null}
  4223. */
  4224. var IOS_VERSION = function () {
  4225. var match = USER_AGENT.match(/OS (\d+)_/i);
  4226. if (match && match[1]) {
  4227. return match[1];
  4228. }
  4229. return null;
  4230. }();
  4231. /**
  4232. * Whether or not this is an Android device.
  4233. *
  4234. * @static
  4235. * @const
  4236. * @type {Boolean}
  4237. */
  4238. var IS_ANDROID = /Android/i.test(USER_AGENT);
  4239. /**
  4240. * The detected Android version - or `null`.
  4241. *
  4242. * @static
  4243. * @const
  4244. * @type {number|string|null}
  4245. */
  4246. var ANDROID_VERSION = function () {
  4247. // This matches Android Major.Minor.Patch versions
  4248. // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  4249. var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
  4250. if (!match) {
  4251. return null;
  4252. }
  4253. var major = match[1] && parseFloat(match[1]);
  4254. var minor = match[2] && parseFloat(match[2]);
  4255. if (major && minor) {
  4256. return parseFloat(match[1] + '.' + match[2]);
  4257. } else if (major) {
  4258. return major;
  4259. }
  4260. return null;
  4261. }();
  4262. /**
  4263. * Whether or not this is a native Android browser.
  4264. *
  4265. * @static
  4266. * @const
  4267. * @type {Boolean}
  4268. */
  4269. var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
  4270. /**
  4271. * Whether or not this is Mozilla Firefox.
  4272. *
  4273. * @static
  4274. * @const
  4275. * @type {Boolean}
  4276. */
  4277. var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
  4278. /**
  4279. * Whether or not this is Microsoft Edge.
  4280. *
  4281. * @static
  4282. * @const
  4283. * @type {Boolean}
  4284. */
  4285. var IS_EDGE = /Edge/i.test(USER_AGENT);
  4286. /**
  4287. * Whether or not this is Google Chrome.
  4288. *
  4289. * This will also be `true` for Chrome on iOS, which will have different support
  4290. * as it is actually Safari under the hood.
  4291. *
  4292. * @static
  4293. * @const
  4294. * @type {Boolean}
  4295. */
  4296. var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
  4297. /**
  4298. * The detected Google Chrome version - or `null`.
  4299. *
  4300. * @static
  4301. * @const
  4302. * @type {number|null}
  4303. */
  4304. var CHROME_VERSION = function () {
  4305. var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
  4306. if (match && match[2]) {
  4307. return parseFloat(match[2]);
  4308. }
  4309. return null;
  4310. }();
  4311. /**
  4312. * The detected Internet Explorer version - or `null`.
  4313. *
  4314. * @static
  4315. * @const
  4316. * @type {number|null}
  4317. */
  4318. var IE_VERSION = function () {
  4319. var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
  4320. var version = result && parseFloat(result[1]);
  4321. if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
  4322. // IE 11 has a different user agent string than other IE versions
  4323. version = 11.0;
  4324. }
  4325. return version;
  4326. }();
  4327. /**
  4328. * Whether or not this is desktop Safari.
  4329. *
  4330. * @static
  4331. * @const
  4332. * @type {Boolean}
  4333. */
  4334. var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
  4335. /**
  4336. * Whether or not this is any flavor of Safari - including iOS.
  4337. *
  4338. * @static
  4339. * @const
  4340. * @type {Boolean}
  4341. */
  4342. var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
  4343. /**
  4344. * Whether or not this is a Windows machine.
  4345. *
  4346. * @static
  4347. * @const
  4348. * @type {Boolean}
  4349. */
  4350. var IS_WINDOWS = /Windows/i.test(USER_AGENT);
  4351. /**
  4352. * Whether or not this device is touch-enabled.
  4353. *
  4354. * @static
  4355. * @const
  4356. * @type {Boolean}
  4357. */
  4358. var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
  4359. var browser = /*#__PURE__*/Object.freeze({
  4360. IS_IPAD: IS_IPAD,
  4361. IS_IPHONE: IS_IPHONE,
  4362. IS_IPOD: IS_IPOD,
  4363. IS_IOS: IS_IOS,
  4364. IOS_VERSION: IOS_VERSION,
  4365. IS_ANDROID: IS_ANDROID,
  4366. ANDROID_VERSION: ANDROID_VERSION,
  4367. IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
  4368. IS_FIREFOX: IS_FIREFOX,
  4369. IS_EDGE: IS_EDGE,
  4370. IS_CHROME: IS_CHROME,
  4371. CHROME_VERSION: CHROME_VERSION,
  4372. IE_VERSION: IE_VERSION,
  4373. IS_SAFARI: IS_SAFARI,
  4374. IS_ANY_SAFARI: IS_ANY_SAFARI,
  4375. IS_WINDOWS: IS_WINDOWS,
  4376. TOUCH_ENABLED: TOUCH_ENABLED
  4377. });
  4378. /**
  4379. * @file time-ranges.js
  4380. * @module time-ranges
  4381. */
  4382. /**
  4383. * Returns the time for the specified index at the start or end
  4384. * of a TimeRange object.
  4385. *
  4386. * @typedef {Function} TimeRangeIndex
  4387. *
  4388. * @param {number} [index=0]
  4389. * The range number to return the time for.
  4390. *
  4391. * @return {number}
  4392. * The time offset at the specified index.
  4393. *
  4394. * @deprecated The index argument must be provided.
  4395. * In the future, leaving it out will throw an error.
  4396. */
  4397. /**
  4398. * An object that contains ranges of time.
  4399. *
  4400. * @typedef {Object} TimeRange
  4401. *
  4402. * @property {number} length
  4403. * The number of time ranges represented by this object.
  4404. *
  4405. * @property {module:time-ranges~TimeRangeIndex} start
  4406. * Returns the time offset at which a specified time range begins.
  4407. *
  4408. * @property {module:time-ranges~TimeRangeIndex} end
  4409. * Returns the time offset at which a specified time range ends.
  4410. *
  4411. * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
  4412. */
  4413. /**
  4414. * Check if any of the time ranges are over the maximum index.
  4415. *
  4416. * @private
  4417. * @param {string} fnName
  4418. * The function name to use for logging
  4419. *
  4420. * @param {number} index
  4421. * The index to check
  4422. *
  4423. * @param {number} maxIndex
  4424. * The maximum possible index
  4425. *
  4426. * @throws {Error} if the timeRanges provided are over the maxIndex
  4427. */
  4428. function rangeCheck(fnName, index, maxIndex) {
  4429. if (typeof index !== 'number' || index < 0 || index > maxIndex) {
  4430. throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
  4431. }
  4432. }
  4433. /**
  4434. * Get the time for the specified index at the start or end
  4435. * of a TimeRange object.
  4436. *
  4437. * @private
  4438. * @param {string} fnName
  4439. * The function name to use for logging
  4440. *
  4441. * @param {string} valueIndex
  4442. * The property that should be used to get the time. should be
  4443. * 'start' or 'end'
  4444. *
  4445. * @param {Array} ranges
  4446. * An array of time ranges
  4447. *
  4448. * @param {Array} [rangeIndex=0]
  4449. * The index to start the search at
  4450. *
  4451. * @return {number}
  4452. * The time that offset at the specified index.
  4453. *
  4454. * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
  4455. * @throws {Error} if rangeIndex is more than the length of ranges
  4456. */
  4457. function getRange(fnName, valueIndex, ranges, rangeIndex) {
  4458. rangeCheck(fnName, rangeIndex, ranges.length - 1);
  4459. return ranges[rangeIndex][valueIndex];
  4460. }
  4461. /**
  4462. * Create a time range object given ranges of time.
  4463. *
  4464. * @private
  4465. * @param {Array} [ranges]
  4466. * An array of time ranges.
  4467. */
  4468. function createTimeRangesObj(ranges) {
  4469. if (ranges === undefined || ranges.length === 0) {
  4470. return {
  4471. length: 0,
  4472. start: function start() {
  4473. throw new Error('This TimeRanges object is empty');
  4474. },
  4475. end: function end() {
  4476. throw new Error('This TimeRanges object is empty');
  4477. }
  4478. };
  4479. }
  4480. return {
  4481. length: ranges.length,
  4482. start: getRange.bind(null, 'start', 0, ranges),
  4483. end: getRange.bind(null, 'end', 1, ranges)
  4484. };
  4485. }
  4486. /**
  4487. * Create a `TimeRange` object which mimics an
  4488. * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
  4489. *
  4490. * @param {number|Array[]} start
  4491. * The start of a single range (a number) or an array of ranges (an
  4492. * array of arrays of two numbers each).
  4493. *
  4494. * @param {number} end
  4495. * The end of a single range. Cannot be used with the array form of
  4496. * the `start` argument.
  4497. */
  4498. function createTimeRanges(start, end) {
  4499. if (Array.isArray(start)) {
  4500. return createTimeRangesObj(start);
  4501. } else if (start === undefined || end === undefined) {
  4502. return createTimeRangesObj();
  4503. }
  4504. return createTimeRangesObj([[start, end]]);
  4505. }
  4506. /**
  4507. * @file buffer.js
  4508. * @module buffer
  4509. */
  4510. /**
  4511. * Compute the percentage of the media that has been buffered.
  4512. *
  4513. * @param {TimeRange} buffered
  4514. * The current `TimeRange` object representing buffered time ranges
  4515. *
  4516. * @param {number} duration
  4517. * Total duration of the media
  4518. *
  4519. * @return {number}
  4520. * Percent buffered of the total duration in decimal form.
  4521. */
  4522. function bufferedPercent(buffered, duration) {
  4523. var bufferedDuration = 0;
  4524. var start;
  4525. var end;
  4526. if (!duration) {
  4527. return 0;
  4528. }
  4529. if (!buffered || !buffered.length) {
  4530. buffered = createTimeRanges(0, 0);
  4531. }
  4532. for (var i = 0; i < buffered.length; i++) {
  4533. start = buffered.start(i);
  4534. end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
  4535. if (end > duration) {
  4536. end = duration;
  4537. }
  4538. bufferedDuration += end - start;
  4539. }
  4540. return bufferedDuration / duration;
  4541. }
  4542. /**
  4543. * @file fullscreen-api.js
  4544. * @module fullscreen-api
  4545. * @private
  4546. */
  4547. /**
  4548. * Store the browser-specific methods for the fullscreen API.
  4549. *
  4550. * @type {Object}
  4551. * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
  4552. * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
  4553. */
  4554. var FullscreenApi = {
  4555. prefixed: true
  4556. }; // browser API methods
  4557. var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
  4558. ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
  4559. ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
  4560. ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
  4561. var specApi = apiMap[0];
  4562. var browserApi; // determine the supported set of functions
  4563. for (var i = 0; i < apiMap.length; i++) {
  4564. // check for exitFullscreen function
  4565. if (apiMap[i][1] in document) {
  4566. browserApi = apiMap[i];
  4567. break;
  4568. }
  4569. } // map the browser API names to the spec API names
  4570. if (browserApi) {
  4571. for (var _i = 0; _i < browserApi.length; _i++) {
  4572. FullscreenApi[specApi[_i]] = browserApi[_i];
  4573. }
  4574. FullscreenApi.prefixed = browserApi[0] !== specApi[0];
  4575. }
  4576. /**
  4577. * @file media-error.js
  4578. */
  4579. /**
  4580. * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
  4581. *
  4582. * @param {number|string|Object|MediaError} value
  4583. * This can be of multiple types:
  4584. * - number: should be a standard error code
  4585. * - string: an error message (the code will be 0)
  4586. * - Object: arbitrary properties
  4587. * - `MediaError` (native): used to populate a video.js `MediaError` object
  4588. * - `MediaError` (video.js): will return itself if it's already a
  4589. * video.js `MediaError` object.
  4590. *
  4591. * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
  4592. * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
  4593. *
  4594. * @class MediaError
  4595. */
  4596. function MediaError(value) {
  4597. // Allow redundant calls to this constructor to avoid having `instanceof`
  4598. // checks peppered around the code.
  4599. if (value instanceof MediaError) {
  4600. return value;
  4601. }
  4602. if (typeof value === 'number') {
  4603. this.code = value;
  4604. } else if (typeof value === 'string') {
  4605. // default code is zero, so this is a custom error
  4606. this.message = value;
  4607. } else if (isObject(value)) {
  4608. // We assign the `code` property manually because native `MediaError` objects
  4609. // do not expose it as an own/enumerable property of the object.
  4610. if (typeof value.code === 'number') {
  4611. this.code = value.code;
  4612. }
  4613. assign(this, value);
  4614. }
  4615. if (!this.message) {
  4616. this.message = MediaError.defaultMessages[this.code] || '';
  4617. }
  4618. }
  4619. /**
  4620. * The error code that refers two one of the defined `MediaError` types
  4621. *
  4622. * @type {Number}
  4623. */
  4624. MediaError.prototype.code = 0;
  4625. /**
  4626. * An optional message that to show with the error. Message is not part of the HTML5
  4627. * video spec but allows for more informative custom errors.
  4628. *
  4629. * @type {String}
  4630. */
  4631. MediaError.prototype.message = '';
  4632. /**
  4633. * An optional status code that can be set by plugins to allow even more detail about
  4634. * the error. For example a plugin might provide a specific HTTP status code and an
  4635. * error message for that code. Then when the plugin gets that error this class will
  4636. * know how to display an error message for it. This allows a custom message to show
  4637. * up on the `Player` error overlay.
  4638. *
  4639. * @type {Array}
  4640. */
  4641. MediaError.prototype.status = null;
  4642. /**
  4643. * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
  4644. * specification listed under {@link MediaError} for more information.
  4645. *
  4646. * @enum {array}
  4647. * @readonly
  4648. * @property {string} 0 - MEDIA_ERR_CUSTOM
  4649. * @property {string} 1 - MEDIA_ERR_ABORTED
  4650. * @property {string} 2 - MEDIA_ERR_NETWORK
  4651. * @property {string} 3 - MEDIA_ERR_DECODE
  4652. * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
  4653. * @property {string} 5 - MEDIA_ERR_ENCRYPTED
  4654. */
  4655. MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
  4656. /**
  4657. * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
  4658. *
  4659. * @type {Array}
  4660. * @constant
  4661. */
  4662. MediaError.defaultMessages = {
  4663. 1: 'You aborted the media playback',
  4664. 2: 'A network error caused the media download to fail part-way.',
  4665. 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
  4666. 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
  4667. 5: 'The media is encrypted and we do not have the keys to decrypt it.'
  4668. }; // Add types as properties on MediaError
  4669. // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
  4670. for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
  4671. MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
  4672. MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
  4673. } // jsdocs for instance/static members added above
  4674. var tuple = SafeParseTuple;
  4675. function SafeParseTuple(obj, reviver) {
  4676. var json;
  4677. var error = null;
  4678. try {
  4679. json = JSON.parse(obj, reviver);
  4680. } catch (err) {
  4681. error = err;
  4682. }
  4683. return [error, json];
  4684. }
  4685. /**
  4686. * Returns whether an object is `Promise`-like (i.e. has a `then` method).
  4687. *
  4688. * @param {Object} value
  4689. * An object that may or may not be `Promise`-like.
  4690. *
  4691. * @return {boolean}
  4692. * Whether or not the object is `Promise`-like.
  4693. */
  4694. function isPromise(value) {
  4695. return value !== undefined && value !== null && typeof value.then === 'function';
  4696. }
  4697. /**
  4698. * Silence a Promise-like object.
  4699. *
  4700. * This is useful for avoiding non-harmful, but potentially confusing "uncaught
  4701. * play promise" rejection error messages.
  4702. *
  4703. * @param {Object} value
  4704. * An object that may or may not be `Promise`-like.
  4705. */
  4706. function silencePromise(value) {
  4707. if (isPromise(value)) {
  4708. value.then(null, function (e) {});
  4709. }
  4710. }
  4711. /**
  4712. * @file text-track-list-converter.js Utilities for capturing text track state and
  4713. * re-creating tracks based on a capture.
  4714. *
  4715. * @module text-track-list-converter
  4716. */
  4717. /**
  4718. * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
  4719. * represents the {@link TextTrack}'s state.
  4720. *
  4721. * @param {TextTrack} track
  4722. * The text track to query.
  4723. *
  4724. * @return {Object}
  4725. * A serializable javascript representation of the TextTrack.
  4726. * @private
  4727. */
  4728. var trackToJson_ = function trackToJson_(track) {
  4729. var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
  4730. if (track[prop]) {
  4731. acc[prop] = track[prop];
  4732. }
  4733. return acc;
  4734. }, {
  4735. cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
  4736. return {
  4737. startTime: cue.startTime,
  4738. endTime: cue.endTime,
  4739. text: cue.text,
  4740. id: cue.id
  4741. };
  4742. })
  4743. });
  4744. return ret;
  4745. };
  4746. /**
  4747. * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
  4748. * state of all {@link TextTrack}s currently configured. The return array is compatible with
  4749. * {@link text-track-list-converter:jsonToTextTracks}.
  4750. *
  4751. * @param {Tech} tech
  4752. * The tech object to query
  4753. *
  4754. * @return {Array}
  4755. * A serializable javascript representation of the {@link Tech}s
  4756. * {@link TextTrackList}.
  4757. */
  4758. var textTracksToJson = function textTracksToJson(tech) {
  4759. var trackEls = tech.$$('track');
  4760. var trackObjs = Array.prototype.map.call(trackEls, function (t) {
  4761. return t.track;
  4762. });
  4763. var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
  4764. var json = trackToJson_(trackEl.track);
  4765. if (trackEl.src) {
  4766. json.src = trackEl.src;
  4767. }
  4768. return json;
  4769. });
  4770. return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
  4771. return trackObjs.indexOf(track) === -1;
  4772. }).map(trackToJson_));
  4773. };
  4774. /**
  4775. * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
  4776. * object {@link TextTrack} representations.
  4777. *
  4778. * @param {Array} json
  4779. * An array of `TextTrack` representation objects, like those that would be
  4780. * produced by `textTracksToJson`.
  4781. *
  4782. * @param {Tech} tech
  4783. * The `Tech` to create the `TextTrack`s on.
  4784. */
  4785. var jsonToTextTracks = function jsonToTextTracks(json, tech) {
  4786. json.forEach(function (track) {
  4787. var addedTrack = tech.addRemoteTextTrack(track).track;
  4788. if (!track.src && track.cues) {
  4789. track.cues.forEach(function (cue) {
  4790. return addedTrack.addCue(cue);
  4791. });
  4792. }
  4793. });
  4794. return tech.textTracks();
  4795. };
  4796. var textTrackConverter = {
  4797. textTracksToJson: textTracksToJson,
  4798. jsonToTextTracks: jsonToTextTracks,
  4799. trackToJson_: trackToJson_
  4800. };
  4801. function createCommonjsModule(fn, module) {
  4802. return module = { exports: {} }, fn(module, module.exports), module.exports;
  4803. }
  4804. var keycode = createCommonjsModule(function (module, exports) {
  4805. // Source: http://jsfiddle.net/vWx8V/
  4806. // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
  4807. /**
  4808. * Conenience method returns corresponding value for given keyName or keyCode.
  4809. *
  4810. * @param {Mixed} keyCode {Number} or keyName {String}
  4811. * @return {Mixed}
  4812. * @api public
  4813. */
  4814. function keyCode(searchInput) {
  4815. // Keyboard Events
  4816. if (searchInput && 'object' === typeof searchInput) {
  4817. var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
  4818. if (hasKeyCode) searchInput = hasKeyCode;
  4819. } // Numbers
  4820. if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
  4821. var search = String(searchInput); // check codes
  4822. var foundNamedKey = codes[search.toLowerCase()];
  4823. if (foundNamedKey) return foundNamedKey; // check aliases
  4824. var foundNamedKey = aliases[search.toLowerCase()];
  4825. if (foundNamedKey) return foundNamedKey; // weird character?
  4826. if (search.length === 1) return search.charCodeAt(0);
  4827. return undefined;
  4828. }
  4829. /**
  4830. * Compares a keyboard event with a given keyCode or keyName.
  4831. *
  4832. * @param {Event} event Keyboard event that should be tested
  4833. * @param {Mixed} keyCode {Number} or keyName {String}
  4834. * @return {Boolean}
  4835. * @api public
  4836. */
  4837. keyCode.isEventKey = function isEventKey(event, nameOrCode) {
  4838. if (event && 'object' === typeof event) {
  4839. var keyCode = event.which || event.keyCode || event.charCode;
  4840. if (keyCode === null || keyCode === undefined) {
  4841. return false;
  4842. }
  4843. if (typeof nameOrCode === 'string') {
  4844. // check codes
  4845. var foundNamedKey = codes[nameOrCode.toLowerCase()];
  4846. if (foundNamedKey) {
  4847. return foundNamedKey === keyCode;
  4848. } // check aliases
  4849. var foundNamedKey = aliases[nameOrCode.toLowerCase()];
  4850. if (foundNamedKey) {
  4851. return foundNamedKey === keyCode;
  4852. }
  4853. } else if (typeof nameOrCode === 'number') {
  4854. return nameOrCode === keyCode;
  4855. }
  4856. return false;
  4857. }
  4858. };
  4859. exports = module.exports = keyCode;
  4860. /**
  4861. * Get by name
  4862. *
  4863. * exports.code['enter'] // => 13
  4864. */
  4865. var codes = exports.code = exports.codes = {
  4866. 'backspace': 8,
  4867. 'tab': 9,
  4868. 'enter': 13,
  4869. 'shift': 16,
  4870. 'ctrl': 17,
  4871. 'alt': 18,
  4872. 'pause/break': 19,
  4873. 'caps lock': 20,
  4874. 'esc': 27,
  4875. 'space': 32,
  4876. 'page up': 33,
  4877. 'page down': 34,
  4878. 'end': 35,
  4879. 'home': 36,
  4880. 'left': 37,
  4881. 'up': 38,
  4882. 'right': 39,
  4883. 'down': 40,
  4884. 'insert': 45,
  4885. 'delete': 46,
  4886. 'command': 91,
  4887. 'left command': 91,
  4888. 'right command': 93,
  4889. 'numpad *': 106,
  4890. 'numpad +': 107,
  4891. 'numpad -': 109,
  4892. 'numpad .': 110,
  4893. 'numpad /': 111,
  4894. 'num lock': 144,
  4895. 'scroll lock': 145,
  4896. 'my computer': 182,
  4897. 'my calculator': 183,
  4898. ';': 186,
  4899. '=': 187,
  4900. ',': 188,
  4901. '-': 189,
  4902. '.': 190,
  4903. '/': 191,
  4904. '`': 192,
  4905. '[': 219,
  4906. '\\': 220,
  4907. ']': 221,
  4908. "'": 222 // Helper aliases
  4909. };
  4910. var aliases = exports.aliases = {
  4911. 'windows': 91,
  4912. '⇧': 16,
  4913. '⌥': 18,
  4914. '⌃': 17,
  4915. '⌘': 91,
  4916. 'ctl': 17,
  4917. 'control': 17,
  4918. 'option': 18,
  4919. 'pause': 19,
  4920. 'break': 19,
  4921. 'caps': 20,
  4922. 'return': 13,
  4923. 'escape': 27,
  4924. 'spc': 32,
  4925. 'spacebar': 32,
  4926. 'pgup': 33,
  4927. 'pgdn': 34,
  4928. 'ins': 45,
  4929. 'del': 46,
  4930. 'cmd': 91
  4931. /*!
  4932. * Programatically add the following
  4933. */
  4934. // lower case chars
  4935. };
  4936. for (i = 97; i < 123; i++) {
  4937. codes[String.fromCharCode(i)] = i - 32;
  4938. } // numbers
  4939. for (var i = 48; i < 58; i++) {
  4940. codes[i - 48] = i;
  4941. } // function keys
  4942. for (i = 1; i < 13; i++) {
  4943. codes['f' + i] = i + 111;
  4944. } // numpad keys
  4945. for (i = 0; i < 10; i++) {
  4946. codes['numpad ' + i] = i + 96;
  4947. }
  4948. /**
  4949. * Get by code
  4950. *
  4951. * exports.name[13] // => 'Enter'
  4952. */
  4953. var names = exports.names = exports.title = {}; // title for backward compat
  4954. // Create reverse mapping
  4955. for (i in codes) {
  4956. names[codes[i]] = i;
  4957. } // Add aliases
  4958. for (var alias in aliases) {
  4959. codes[alias] = aliases[alias];
  4960. }
  4961. });
  4962. var keycode_1 = keycode.code;
  4963. var keycode_2 = keycode.codes;
  4964. var keycode_3 = keycode.aliases;
  4965. var keycode_4 = keycode.names;
  4966. var keycode_5 = keycode.title;
  4967. var MODAL_CLASS_NAME = 'vjs-modal-dialog';
  4968. /**
  4969. * The `ModalDialog` displays over the video and its controls, which blocks
  4970. * interaction with the player until it is closed.
  4971. *
  4972. * Modal dialogs include a "Close" button and will close when that button
  4973. * is activated - or when ESC is pressed anywhere.
  4974. *
  4975. * @extends Component
  4976. */
  4977. var ModalDialog =
  4978. /*#__PURE__*/
  4979. function (_Component) {
  4980. _inheritsLoose(ModalDialog, _Component);
  4981. /**
  4982. * Create an instance of this class.
  4983. *
  4984. * @param {Player} player
  4985. * The `Player` that this class should be attached to.
  4986. *
  4987. * @param {Object} [options]
  4988. * The key/value store of player options.
  4989. *
  4990. * @param {Mixed} [options.content=undefined]
  4991. * Provide customized content for this modal.
  4992. *
  4993. * @param {string} [options.description]
  4994. * A text description for the modal, primarily for accessibility.
  4995. *
  4996. * @param {boolean} [options.fillAlways=false]
  4997. * Normally, modals are automatically filled only the first time
  4998. * they open. This tells the modal to refresh its content
  4999. * every time it opens.
  5000. *
  5001. * @param {string} [options.label]
  5002. * A text label for the modal, primarily for accessibility.
  5003. *
  5004. * @param {boolean} [options.pauseOnOpen=true]
  5005. * If `true`, playback will will be paused if playing when
  5006. * the modal opens, and resumed when it closes.
  5007. *
  5008. * @param {boolean} [options.temporary=true]
  5009. * If `true`, the modal can only be opened once; it will be
  5010. * disposed as soon as it's closed.
  5011. *
  5012. * @param {boolean} [options.uncloseable=false]
  5013. * If `true`, the user will not be able to close the modal
  5014. * through the UI in the normal ways. Programmatic closing is
  5015. * still possible.
  5016. */
  5017. function ModalDialog(player, options) {
  5018. var _this;
  5019. _this = _Component.call(this, player, options) || this;
  5020. _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
  5021. _this.closeable(!_this.options_.uncloseable);
  5022. _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
  5023. // because we only want the contents of the modal in the contentEl
  5024. // (not the UI elements like the close button).
  5025. _this.contentEl_ = createEl('div', {
  5026. className: MODAL_CLASS_NAME + "-content"
  5027. }, {
  5028. role: 'document'
  5029. });
  5030. _this.descEl_ = createEl('p', {
  5031. className: MODAL_CLASS_NAME + "-description vjs-control-text",
  5032. id: _this.el().getAttribute('aria-describedby')
  5033. });
  5034. textContent(_this.descEl_, _this.description());
  5035. _this.el_.appendChild(_this.descEl_);
  5036. _this.el_.appendChild(_this.contentEl_);
  5037. return _this;
  5038. }
  5039. /**
  5040. * Create the `ModalDialog`'s DOM element
  5041. *
  5042. * @return {Element}
  5043. * The DOM element that gets created.
  5044. */
  5045. var _proto = ModalDialog.prototype;
  5046. _proto.createEl = function createEl() {
  5047. return _Component.prototype.createEl.call(this, 'div', {
  5048. className: this.buildCSSClass(),
  5049. tabIndex: -1
  5050. }, {
  5051. 'aria-describedby': this.id() + "_description",
  5052. 'aria-hidden': 'true',
  5053. 'aria-label': this.label(),
  5054. 'role': 'dialog'
  5055. });
  5056. };
  5057. _proto.dispose = function dispose() {
  5058. this.contentEl_ = null;
  5059. this.descEl_ = null;
  5060. this.previouslyActiveEl_ = null;
  5061. _Component.prototype.dispose.call(this);
  5062. }
  5063. /**
  5064. * Builds the default DOM `className`.
  5065. *
  5066. * @return {string}
  5067. * The DOM `className` for this object.
  5068. */
  5069. ;
  5070. _proto.buildCSSClass = function buildCSSClass() {
  5071. return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
  5072. }
  5073. /**
  5074. * Returns the label string for this modal. Primarily used for accessibility.
  5075. *
  5076. * @return {string}
  5077. * the localized or raw label of this modal.
  5078. */
  5079. ;
  5080. _proto.label = function label() {
  5081. return this.localize(this.options_.label || 'Modal Window');
  5082. }
  5083. /**
  5084. * Returns the description string for this modal. Primarily used for
  5085. * accessibility.
  5086. *
  5087. * @return {string}
  5088. * The localized or raw description of this modal.
  5089. */
  5090. ;
  5091. _proto.description = function description() {
  5092. var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
  5093. if (this.closeable()) {
  5094. desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
  5095. }
  5096. return desc;
  5097. }
  5098. /**
  5099. * Opens the modal.
  5100. *
  5101. * @fires ModalDialog#beforemodalopen
  5102. * @fires ModalDialog#modalopen
  5103. */
  5104. ;
  5105. _proto.open = function open() {
  5106. if (!this.opened_) {
  5107. var player = this.player();
  5108. /**
  5109. * Fired just before a `ModalDialog` is opened.
  5110. *
  5111. * @event ModalDialog#beforemodalopen
  5112. * @type {EventTarget~Event}
  5113. */
  5114. this.trigger('beforemodalopen');
  5115. this.opened_ = true; // Fill content if the modal has never opened before and
  5116. // never been filled.
  5117. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
  5118. this.fill();
  5119. } // If the player was playing, pause it and take note of its previously
  5120. // playing state.
  5121. this.wasPlaying_ = !player.paused();
  5122. if (this.options_.pauseOnOpen && this.wasPlaying_) {
  5123. player.pause();
  5124. }
  5125. this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled.
  5126. this.hadControls_ = player.controls();
  5127. player.controls(false);
  5128. this.show();
  5129. this.conditionalFocus_();
  5130. this.el().setAttribute('aria-hidden', 'false');
  5131. /**
  5132. * Fired just after a `ModalDialog` is opened.
  5133. *
  5134. * @event ModalDialog#modalopen
  5135. * @type {EventTarget~Event}
  5136. */
  5137. this.trigger('modalopen');
  5138. this.hasBeenOpened_ = true;
  5139. }
  5140. }
  5141. /**
  5142. * If the `ModalDialog` is currently open or closed.
  5143. *
  5144. * @param {boolean} [value]
  5145. * If given, it will open (`true`) or close (`false`) the modal.
  5146. *
  5147. * @return {boolean}
  5148. * the current open state of the modaldialog
  5149. */
  5150. ;
  5151. _proto.opened = function opened(value) {
  5152. if (typeof value === 'boolean') {
  5153. this[value ? 'open' : 'close']();
  5154. }
  5155. return this.opened_;
  5156. }
  5157. /**
  5158. * Closes the modal, does nothing if the `ModalDialog` is
  5159. * not open.
  5160. *
  5161. * @fires ModalDialog#beforemodalclose
  5162. * @fires ModalDialog#modalclose
  5163. */
  5164. ;
  5165. _proto.close = function close() {
  5166. if (!this.opened_) {
  5167. return;
  5168. }
  5169. var player = this.player();
  5170. /**
  5171. * Fired just before a `ModalDialog` is closed.
  5172. *
  5173. * @event ModalDialog#beforemodalclose
  5174. * @type {EventTarget~Event}
  5175. */
  5176. this.trigger('beforemodalclose');
  5177. this.opened_ = false;
  5178. if (this.wasPlaying_ && this.options_.pauseOnOpen) {
  5179. player.play();
  5180. }
  5181. this.off('keydown', this.handleKeyDown);
  5182. if (this.hadControls_) {
  5183. player.controls(true);
  5184. }
  5185. this.hide();
  5186. this.el().setAttribute('aria-hidden', 'true');
  5187. /**
  5188. * Fired just after a `ModalDialog` is closed.
  5189. *
  5190. * @event ModalDialog#modalclose
  5191. * @type {EventTarget~Event}
  5192. */
  5193. this.trigger('modalclose');
  5194. this.conditionalBlur_();
  5195. if (this.options_.temporary) {
  5196. this.dispose();
  5197. }
  5198. }
  5199. /**
  5200. * Check to see if the `ModalDialog` is closeable via the UI.
  5201. *
  5202. * @param {boolean} [value]
  5203. * If given as a boolean, it will set the `closeable` option.
  5204. *
  5205. * @return {boolean}
  5206. * Returns the final value of the closable option.
  5207. */
  5208. ;
  5209. _proto.closeable = function closeable(value) {
  5210. if (typeof value === 'boolean') {
  5211. var closeable = this.closeable_ = !!value;
  5212. var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
  5213. if (closeable && !close) {
  5214. // The close button should be a child of the modal - not its
  5215. // content element, so temporarily change the content element.
  5216. var temp = this.contentEl_;
  5217. this.contentEl_ = this.el_;
  5218. close = this.addChild('closeButton', {
  5219. controlText: 'Close Modal Dialog'
  5220. });
  5221. this.contentEl_ = temp;
  5222. this.on(close, 'close', this.close);
  5223. } // If this is being made uncloseable and has a close button, remove it.
  5224. if (!closeable && close) {
  5225. this.off(close, 'close', this.close);
  5226. this.removeChild(close);
  5227. close.dispose();
  5228. }
  5229. }
  5230. return this.closeable_;
  5231. }
  5232. /**
  5233. * Fill the modal's content element with the modal's "content" option.
  5234. * The content element will be emptied before this change takes place.
  5235. */
  5236. ;
  5237. _proto.fill = function fill() {
  5238. this.fillWith(this.content());
  5239. }
  5240. /**
  5241. * Fill the modal's content element with arbitrary content.
  5242. * The content element will be emptied before this change takes place.
  5243. *
  5244. * @fires ModalDialog#beforemodalfill
  5245. * @fires ModalDialog#modalfill
  5246. *
  5247. * @param {Mixed} [content]
  5248. * The same rules apply to this as apply to the `content` option.
  5249. */
  5250. ;
  5251. _proto.fillWith = function fillWith(content) {
  5252. var contentEl = this.contentEl();
  5253. var parentEl = contentEl.parentNode;
  5254. var nextSiblingEl = contentEl.nextSibling;
  5255. /**
  5256. * Fired just before a `ModalDialog` is filled with content.
  5257. *
  5258. * @event ModalDialog#beforemodalfill
  5259. * @type {EventTarget~Event}
  5260. */
  5261. this.trigger('beforemodalfill');
  5262. this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
  5263. // manipulation to avoid modifying the live DOM multiple times.
  5264. parentEl.removeChild(contentEl);
  5265. this.empty();
  5266. insertContent(contentEl, content);
  5267. /**
  5268. * Fired just after a `ModalDialog` is filled with content.
  5269. *
  5270. * @event ModalDialog#modalfill
  5271. * @type {EventTarget~Event}
  5272. */
  5273. this.trigger('modalfill'); // Re-inject the re-filled content element.
  5274. if (nextSiblingEl) {
  5275. parentEl.insertBefore(contentEl, nextSiblingEl);
  5276. } else {
  5277. parentEl.appendChild(contentEl);
  5278. } // make sure that the close button is last in the dialog DOM
  5279. var closeButton = this.getChild('closeButton');
  5280. if (closeButton) {
  5281. parentEl.appendChild(closeButton.el_);
  5282. }
  5283. }
  5284. /**
  5285. * Empties the content element. This happens anytime the modal is filled.
  5286. *
  5287. * @fires ModalDialog#beforemodalempty
  5288. * @fires ModalDialog#modalempty
  5289. */
  5290. ;
  5291. _proto.empty = function empty() {
  5292. /**
  5293. * Fired just before a `ModalDialog` is emptied.
  5294. *
  5295. * @event ModalDialog#beforemodalempty
  5296. * @type {EventTarget~Event}
  5297. */
  5298. this.trigger('beforemodalempty');
  5299. emptyEl(this.contentEl());
  5300. /**
  5301. * Fired just after a `ModalDialog` is emptied.
  5302. *
  5303. * @event ModalDialog#modalempty
  5304. * @type {EventTarget~Event}
  5305. */
  5306. this.trigger('modalempty');
  5307. }
  5308. /**
  5309. * Gets or sets the modal content, which gets normalized before being
  5310. * rendered into the DOM.
  5311. *
  5312. * This does not update the DOM or fill the modal, but it is called during
  5313. * that process.
  5314. *
  5315. * @param {Mixed} [value]
  5316. * If defined, sets the internal content value to be used on the
  5317. * next call(s) to `fill`. This value is normalized before being
  5318. * inserted. To "clear" the internal content value, pass `null`.
  5319. *
  5320. * @return {Mixed}
  5321. * The current content of the modal dialog
  5322. */
  5323. ;
  5324. _proto.content = function content(value) {
  5325. if (typeof value !== 'undefined') {
  5326. this.content_ = value;
  5327. }
  5328. return this.content_;
  5329. }
  5330. /**
  5331. * conditionally focus the modal dialog if focus was previously on the player.
  5332. *
  5333. * @private
  5334. */
  5335. ;
  5336. _proto.conditionalFocus_ = function conditionalFocus_() {
  5337. var activeEl = document.activeElement;
  5338. var playerEl = this.player_.el_;
  5339. this.previouslyActiveEl_ = null;
  5340. if (playerEl.contains(activeEl) || playerEl === activeEl) {
  5341. this.previouslyActiveEl_ = activeEl;
  5342. this.focus();
  5343. }
  5344. }
  5345. /**
  5346. * conditionally blur the element and refocus the last focused element
  5347. *
  5348. * @private
  5349. */
  5350. ;
  5351. _proto.conditionalBlur_ = function conditionalBlur_() {
  5352. if (this.previouslyActiveEl_) {
  5353. this.previouslyActiveEl_.focus();
  5354. this.previouslyActiveEl_ = null;
  5355. }
  5356. }
  5357. /**
  5358. * Keydown handler. Attached when modal is focused.
  5359. *
  5360. * @listens keydown
  5361. */
  5362. ;
  5363. _proto.handleKeyDown = function handleKeyDown(event) {
  5364. // Do not allow keydowns to reach out of the modal dialog.
  5365. event.stopPropagation();
  5366. if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
  5367. event.preventDefault();
  5368. this.close();
  5369. return;
  5370. } // exit early if it isn't a tab key
  5371. if (!keycode.isEventKey(event, 'Tab')) {
  5372. return;
  5373. }
  5374. var focusableEls = this.focusableEls_();
  5375. var activeEl = this.el_.querySelector(':focus');
  5376. var focusIndex;
  5377. for (var i = 0; i < focusableEls.length; i++) {
  5378. if (activeEl === focusableEls[i]) {
  5379. focusIndex = i;
  5380. break;
  5381. }
  5382. }
  5383. if (document.activeElement === this.el_) {
  5384. focusIndex = 0;
  5385. }
  5386. if (event.shiftKey && focusIndex === 0) {
  5387. focusableEls[focusableEls.length - 1].focus();
  5388. event.preventDefault();
  5389. } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
  5390. focusableEls[0].focus();
  5391. event.preventDefault();
  5392. }
  5393. }
  5394. /**
  5395. * get all focusable elements
  5396. *
  5397. * @private
  5398. */
  5399. ;
  5400. _proto.focusableEls_ = function focusableEls_() {
  5401. var allChildren = this.el_.querySelectorAll('*');
  5402. return Array.prototype.filter.call(allChildren, function (child) {
  5403. 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');
  5404. });
  5405. };
  5406. return ModalDialog;
  5407. }(Component);
  5408. /**
  5409. * Default options for `ModalDialog` default options.
  5410. *
  5411. * @type {Object}
  5412. * @private
  5413. */
  5414. ModalDialog.prototype.options_ = {
  5415. pauseOnOpen: true,
  5416. temporary: true
  5417. };
  5418. Component.registerComponent('ModalDialog', ModalDialog);
  5419. /**
  5420. * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
  5421. * {@link VideoTrackList}
  5422. *
  5423. * @extends EventTarget
  5424. */
  5425. var TrackList =
  5426. /*#__PURE__*/
  5427. function (_EventTarget) {
  5428. _inheritsLoose(TrackList, _EventTarget);
  5429. /**
  5430. * Create an instance of this class
  5431. *
  5432. * @param {Track[]} tracks
  5433. * A list of tracks to initialize the list with.
  5434. *
  5435. * @abstract
  5436. */
  5437. function TrackList(tracks) {
  5438. var _this;
  5439. if (tracks === void 0) {
  5440. tracks = [];
  5441. }
  5442. _this = _EventTarget.call(this) || this;
  5443. _this.tracks_ = [];
  5444. /**
  5445. * @memberof TrackList
  5446. * @member {number} length
  5447. * The current number of `Track`s in the this Trackist.
  5448. * @instance
  5449. */
  5450. Object.defineProperty(_assertThisInitialized(_this), 'length', {
  5451. get: function get() {
  5452. return this.tracks_.length;
  5453. }
  5454. });
  5455. for (var i = 0; i < tracks.length; i++) {
  5456. _this.addTrack(tracks[i]);
  5457. }
  5458. return _this;
  5459. }
  5460. /**
  5461. * Add a {@link Track} to the `TrackList`
  5462. *
  5463. * @param {Track} track
  5464. * The audio, video, or text track to add to the list.
  5465. *
  5466. * @fires TrackList#addtrack
  5467. */
  5468. var _proto = TrackList.prototype;
  5469. _proto.addTrack = function addTrack(track) {
  5470. var index = this.tracks_.length;
  5471. if (!('' + index in this)) {
  5472. Object.defineProperty(this, index, {
  5473. get: function get() {
  5474. return this.tracks_[index];
  5475. }
  5476. });
  5477. } // Do not add duplicate tracks
  5478. if (this.tracks_.indexOf(track) === -1) {
  5479. this.tracks_.push(track);
  5480. /**
  5481. * Triggered when a track is added to a track list.
  5482. *
  5483. * @event TrackList#addtrack
  5484. * @type {EventTarget~Event}
  5485. * @property {Track} track
  5486. * A reference to track that was added.
  5487. */
  5488. this.trigger({
  5489. track: track,
  5490. type: 'addtrack',
  5491. target: this
  5492. });
  5493. }
  5494. }
  5495. /**
  5496. * Remove a {@link Track} from the `TrackList`
  5497. *
  5498. * @param {Track} rtrack
  5499. * The audio, video, or text track to remove from the list.
  5500. *
  5501. * @fires TrackList#removetrack
  5502. */
  5503. ;
  5504. _proto.removeTrack = function removeTrack(rtrack) {
  5505. var track;
  5506. for (var i = 0, l = this.length; i < l; i++) {
  5507. if (this[i] === rtrack) {
  5508. track = this[i];
  5509. if (track.off) {
  5510. track.off();
  5511. }
  5512. this.tracks_.splice(i, 1);
  5513. break;
  5514. }
  5515. }
  5516. if (!track) {
  5517. return;
  5518. }
  5519. /**
  5520. * Triggered when a track is removed from track list.
  5521. *
  5522. * @event TrackList#removetrack
  5523. * @type {EventTarget~Event}
  5524. * @property {Track} track
  5525. * A reference to track that was removed.
  5526. */
  5527. this.trigger({
  5528. track: track,
  5529. type: 'removetrack',
  5530. target: this
  5531. });
  5532. }
  5533. /**
  5534. * Get a Track from the TrackList by a tracks id
  5535. *
  5536. * @param {string} id - the id of the track to get
  5537. * @method getTrackById
  5538. * @return {Track}
  5539. * @private
  5540. */
  5541. ;
  5542. _proto.getTrackById = function getTrackById(id) {
  5543. var result = null;
  5544. for (var i = 0, l = this.length; i < l; i++) {
  5545. var track = this[i];
  5546. if (track.id === id) {
  5547. result = track;
  5548. break;
  5549. }
  5550. }
  5551. return result;
  5552. };
  5553. return TrackList;
  5554. }(EventTarget);
  5555. /**
  5556. * Triggered when a different track is selected/enabled.
  5557. *
  5558. * @event TrackList#change
  5559. * @type {EventTarget~Event}
  5560. */
  5561. /**
  5562. * Events that can be called with on + eventName. See {@link EventHandler}.
  5563. *
  5564. * @property {Object} TrackList#allowedEvents_
  5565. * @private
  5566. */
  5567. TrackList.prototype.allowedEvents_ = {
  5568. change: 'change',
  5569. addtrack: 'addtrack',
  5570. removetrack: 'removetrack'
  5571. }; // emulate attribute EventHandler support to allow for feature detection
  5572. for (var event in TrackList.prototype.allowedEvents_) {
  5573. TrackList.prototype['on' + event] = null;
  5574. }
  5575. /**
  5576. * Anywhere we call this function we diverge from the spec
  5577. * as we only support one enabled audiotrack at a time
  5578. *
  5579. * @param {AudioTrackList} list
  5580. * list to work on
  5581. *
  5582. * @param {AudioTrack} track
  5583. * The track to skip
  5584. *
  5585. * @private
  5586. */
  5587. var disableOthers = function disableOthers(list, track) {
  5588. for (var i = 0; i < list.length; i++) {
  5589. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5590. continue;
  5591. } // another audio track is enabled, disable it
  5592. list[i].enabled = false;
  5593. }
  5594. };
  5595. /**
  5596. * The current list of {@link AudioTrack} for a media file.
  5597. *
  5598. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
  5599. * @extends TrackList
  5600. */
  5601. var AudioTrackList =
  5602. /*#__PURE__*/
  5603. function (_TrackList) {
  5604. _inheritsLoose(AudioTrackList, _TrackList);
  5605. /**
  5606. * Create an instance of this class.
  5607. *
  5608. * @param {AudioTrack[]} [tracks=[]]
  5609. * A list of `AudioTrack` to instantiate the list with.
  5610. */
  5611. function AudioTrackList(tracks) {
  5612. var _this;
  5613. if (tracks === void 0) {
  5614. tracks = [];
  5615. }
  5616. // make sure only 1 track is enabled
  5617. // sorted from last index to first index
  5618. for (var i = tracks.length - 1; i >= 0; i--) {
  5619. if (tracks[i].enabled) {
  5620. disableOthers(tracks, tracks[i]);
  5621. break;
  5622. }
  5623. }
  5624. _this = _TrackList.call(this, tracks) || this;
  5625. _this.changing_ = false;
  5626. return _this;
  5627. }
  5628. /**
  5629. * Add an {@link AudioTrack} to the `AudioTrackList`.
  5630. *
  5631. * @param {AudioTrack} track
  5632. * The AudioTrack to add to the list
  5633. *
  5634. * @fires TrackList#addtrack
  5635. */
  5636. var _proto = AudioTrackList.prototype;
  5637. _proto.addTrack = function addTrack(track) {
  5638. var _this2 = this;
  5639. if (track.enabled) {
  5640. disableOthers(this, track);
  5641. }
  5642. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5643. if (!track.addEventListener) {
  5644. return;
  5645. }
  5646. track.enabledChange_ = function () {
  5647. // when we are disabling other tracks (since we don't support
  5648. // more than one track at a time) we will set changing_
  5649. // to true so that we don't trigger additional change events
  5650. if (_this2.changing_) {
  5651. return;
  5652. }
  5653. _this2.changing_ = true;
  5654. disableOthers(_this2, track);
  5655. _this2.changing_ = false;
  5656. _this2.trigger('change');
  5657. };
  5658. /**
  5659. * @listens AudioTrack#enabledchange
  5660. * @fires TrackList#change
  5661. */
  5662. track.addEventListener('enabledchange', track.enabledChange_);
  5663. };
  5664. _proto.removeTrack = function removeTrack(rtrack) {
  5665. _TrackList.prototype.removeTrack.call(this, rtrack);
  5666. if (rtrack.removeEventListener && rtrack.enabledChange_) {
  5667. rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
  5668. rtrack.enabledChange_ = null;
  5669. }
  5670. };
  5671. return AudioTrackList;
  5672. }(TrackList);
  5673. /**
  5674. * Un-select all other {@link VideoTrack}s that are selected.
  5675. *
  5676. * @param {VideoTrackList} list
  5677. * list to work on
  5678. *
  5679. * @param {VideoTrack} track
  5680. * The track to skip
  5681. *
  5682. * @private
  5683. */
  5684. var disableOthers$1 = function disableOthers(list, track) {
  5685. for (var i = 0; i < list.length; i++) {
  5686. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5687. continue;
  5688. } // another video track is enabled, disable it
  5689. list[i].selected = false;
  5690. }
  5691. };
  5692. /**
  5693. * The current list of {@link VideoTrack} for a video.
  5694. *
  5695. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
  5696. * @extends TrackList
  5697. */
  5698. var VideoTrackList =
  5699. /*#__PURE__*/
  5700. function (_TrackList) {
  5701. _inheritsLoose(VideoTrackList, _TrackList);
  5702. /**
  5703. * Create an instance of this class.
  5704. *
  5705. * @param {VideoTrack[]} [tracks=[]]
  5706. * A list of `VideoTrack` to instantiate the list with.
  5707. */
  5708. function VideoTrackList(tracks) {
  5709. var _this;
  5710. if (tracks === void 0) {
  5711. tracks = [];
  5712. }
  5713. // make sure only 1 track is enabled
  5714. // sorted from last index to first index
  5715. for (var i = tracks.length - 1; i >= 0; i--) {
  5716. if (tracks[i].selected) {
  5717. disableOthers$1(tracks, tracks[i]);
  5718. break;
  5719. }
  5720. }
  5721. _this = _TrackList.call(this, tracks) || this;
  5722. _this.changing_ = false;
  5723. /**
  5724. * @member {number} VideoTrackList#selectedIndex
  5725. * The current index of the selected {@link VideoTrack`}.
  5726. */
  5727. Object.defineProperty(_assertThisInitialized(_this), 'selectedIndex', {
  5728. get: function get() {
  5729. for (var _i = 0; _i < this.length; _i++) {
  5730. if (this[_i].selected) {
  5731. return _i;
  5732. }
  5733. }
  5734. return -1;
  5735. },
  5736. set: function set() {}
  5737. });
  5738. return _this;
  5739. }
  5740. /**
  5741. * Add a {@link VideoTrack} to the `VideoTrackList`.
  5742. *
  5743. * @param {VideoTrack} track
  5744. * The VideoTrack to add to the list
  5745. *
  5746. * @fires TrackList#addtrack
  5747. */
  5748. var _proto = VideoTrackList.prototype;
  5749. _proto.addTrack = function addTrack(track) {
  5750. var _this2 = this;
  5751. if (track.selected) {
  5752. disableOthers$1(this, track);
  5753. }
  5754. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5755. if (!track.addEventListener) {
  5756. return;
  5757. }
  5758. track.selectedChange_ = function () {
  5759. if (_this2.changing_) {
  5760. return;
  5761. }
  5762. _this2.changing_ = true;
  5763. disableOthers$1(_this2, track);
  5764. _this2.changing_ = false;
  5765. _this2.trigger('change');
  5766. };
  5767. /**
  5768. * @listens VideoTrack#selectedchange
  5769. * @fires TrackList#change
  5770. */
  5771. track.addEventListener('selectedchange', track.selectedChange_);
  5772. };
  5773. _proto.removeTrack = function removeTrack(rtrack) {
  5774. _TrackList.prototype.removeTrack.call(this, rtrack);
  5775. if (rtrack.removeEventListener && rtrack.selectedChange_) {
  5776. rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
  5777. rtrack.selectedChange_ = null;
  5778. }
  5779. };
  5780. return VideoTrackList;
  5781. }(TrackList);
  5782. /**
  5783. * The current list of {@link TextTrack} for a media file.
  5784. *
  5785. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
  5786. * @extends TrackList
  5787. */
  5788. var TextTrackList =
  5789. /*#__PURE__*/
  5790. function (_TrackList) {
  5791. _inheritsLoose(TextTrackList, _TrackList);
  5792. function TextTrackList() {
  5793. return _TrackList.apply(this, arguments) || this;
  5794. }
  5795. var _proto = TextTrackList.prototype;
  5796. /**
  5797. * Add a {@link TextTrack} to the `TextTrackList`
  5798. *
  5799. * @param {TextTrack} track
  5800. * The text track to add to the list.
  5801. *
  5802. * @fires TrackList#addtrack
  5803. */
  5804. _proto.addTrack = function addTrack(track) {
  5805. var _this = this;
  5806. _TrackList.prototype.addTrack.call(this, track);
  5807. if (!this.queueChange_) {
  5808. this.queueChange_ = function () {
  5809. return _this.queueTrigger('change');
  5810. };
  5811. }
  5812. if (!this.triggerSelectedlanguagechange) {
  5813. this.triggerSelectedlanguagechange_ = function () {
  5814. return _this.trigger('selectedlanguagechange');
  5815. };
  5816. }
  5817. /**
  5818. * @listens TextTrack#modechange
  5819. * @fires TrackList#change
  5820. */
  5821. track.addEventListener('modechange', this.queueChange_);
  5822. var nonLanguageTextTrackKind = ['metadata', 'chapters'];
  5823. if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
  5824. track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
  5825. }
  5826. };
  5827. _proto.removeTrack = function removeTrack(rtrack) {
  5828. _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
  5829. if (rtrack.removeEventListener) {
  5830. if (this.queueChange_) {
  5831. rtrack.removeEventListener('modechange', this.queueChange_);
  5832. }
  5833. if (this.selectedlanguagechange_) {
  5834. rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
  5835. }
  5836. }
  5837. };
  5838. return TextTrackList;
  5839. }(TrackList);
  5840. /**
  5841. * @file html-track-element-list.js
  5842. */
  5843. /**
  5844. * The current list of {@link HtmlTrackElement}s.
  5845. */
  5846. var HtmlTrackElementList =
  5847. /*#__PURE__*/
  5848. function () {
  5849. /**
  5850. * Create an instance of this class.
  5851. *
  5852. * @param {HtmlTrackElement[]} [tracks=[]]
  5853. * A list of `HtmlTrackElement` to instantiate the list with.
  5854. */
  5855. function HtmlTrackElementList(trackElements) {
  5856. if (trackElements === void 0) {
  5857. trackElements = [];
  5858. }
  5859. this.trackElements_ = [];
  5860. /**
  5861. * @memberof HtmlTrackElementList
  5862. * @member {number} length
  5863. * The current number of `Track`s in the this Trackist.
  5864. * @instance
  5865. */
  5866. Object.defineProperty(this, 'length', {
  5867. get: function get() {
  5868. return this.trackElements_.length;
  5869. }
  5870. });
  5871. for (var i = 0, length = trackElements.length; i < length; i++) {
  5872. this.addTrackElement_(trackElements[i]);
  5873. }
  5874. }
  5875. /**
  5876. * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
  5877. *
  5878. * @param {HtmlTrackElement} trackElement
  5879. * The track element to add to the list.
  5880. *
  5881. * @private
  5882. */
  5883. var _proto = HtmlTrackElementList.prototype;
  5884. _proto.addTrackElement_ = function addTrackElement_(trackElement) {
  5885. var index = this.trackElements_.length;
  5886. if (!('' + index in this)) {
  5887. Object.defineProperty(this, index, {
  5888. get: function get() {
  5889. return this.trackElements_[index];
  5890. }
  5891. });
  5892. } // Do not add duplicate elements
  5893. if (this.trackElements_.indexOf(trackElement) === -1) {
  5894. this.trackElements_.push(trackElement);
  5895. }
  5896. }
  5897. /**
  5898. * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
  5899. * {@link TextTrack}.
  5900. *
  5901. * @param {TextTrack} track
  5902. * The track associated with a track element.
  5903. *
  5904. * @return {HtmlTrackElement|undefined}
  5905. * The track element that was found or undefined.
  5906. *
  5907. * @private
  5908. */
  5909. ;
  5910. _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
  5911. var trackElement_;
  5912. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5913. if (track === this.trackElements_[i].track) {
  5914. trackElement_ = this.trackElements_[i];
  5915. break;
  5916. }
  5917. }
  5918. return trackElement_;
  5919. }
  5920. /**
  5921. * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
  5922. *
  5923. * @param {HtmlTrackElement} trackElement
  5924. * The track element to remove from the list.
  5925. *
  5926. * @private
  5927. */
  5928. ;
  5929. _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
  5930. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5931. if (trackElement === this.trackElements_[i]) {
  5932. if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
  5933. this.trackElements_[i].track.off();
  5934. }
  5935. if (typeof this.trackElements_[i].off === 'function') {
  5936. this.trackElements_[i].off();
  5937. }
  5938. this.trackElements_.splice(i, 1);
  5939. break;
  5940. }
  5941. }
  5942. };
  5943. return HtmlTrackElementList;
  5944. }();
  5945. /**
  5946. * @file text-track-cue-list.js
  5947. */
  5948. /**
  5949. * @typedef {Object} TextTrackCueList~TextTrackCue
  5950. *
  5951. * @property {string} id
  5952. * The unique id for this text track cue
  5953. *
  5954. * @property {number} startTime
  5955. * The start time for this text track cue
  5956. *
  5957. * @property {number} endTime
  5958. * The end time for this text track cue
  5959. *
  5960. * @property {boolean} pauseOnExit
  5961. * Pause when the end time is reached if true.
  5962. *
  5963. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
  5964. */
  5965. /**
  5966. * A List of TextTrackCues.
  5967. *
  5968. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
  5969. */
  5970. var TextTrackCueList =
  5971. /*#__PURE__*/
  5972. function () {
  5973. /**
  5974. * Create an instance of this class..
  5975. *
  5976. * @param {Array} cues
  5977. * A list of cues to be initialized with
  5978. */
  5979. function TextTrackCueList(cues) {
  5980. TextTrackCueList.prototype.setCues_.call(this, cues);
  5981. /**
  5982. * @memberof TextTrackCueList
  5983. * @member {number} length
  5984. * The current number of `TextTrackCue`s in the TextTrackCueList.
  5985. * @instance
  5986. */
  5987. Object.defineProperty(this, 'length', {
  5988. get: function get() {
  5989. return this.length_;
  5990. }
  5991. });
  5992. }
  5993. /**
  5994. * A setter for cues in this list. Creates getters
  5995. * an an index for the cues.
  5996. *
  5997. * @param {Array} cues
  5998. * An array of cues to set
  5999. *
  6000. * @private
  6001. */
  6002. var _proto = TextTrackCueList.prototype;
  6003. _proto.setCues_ = function setCues_(cues) {
  6004. var oldLength = this.length || 0;
  6005. var i = 0;
  6006. var l = cues.length;
  6007. this.cues_ = cues;
  6008. this.length_ = cues.length;
  6009. var defineProp = function defineProp(index) {
  6010. if (!('' + index in this)) {
  6011. Object.defineProperty(this, '' + index, {
  6012. get: function get() {
  6013. return this.cues_[index];
  6014. }
  6015. });
  6016. }
  6017. };
  6018. if (oldLength < l) {
  6019. i = oldLength;
  6020. for (; i < l; i++) {
  6021. defineProp.call(this, i);
  6022. }
  6023. }
  6024. }
  6025. /**
  6026. * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
  6027. *
  6028. * @param {string} id
  6029. * The id of the cue that should be searched for.
  6030. *
  6031. * @return {TextTrackCueList~TextTrackCue|null}
  6032. * A single cue or null if none was found.
  6033. */
  6034. ;
  6035. _proto.getCueById = function getCueById(id) {
  6036. var result = null;
  6037. for (var i = 0, l = this.length; i < l; i++) {
  6038. var cue = this[i];
  6039. if (cue.id === id) {
  6040. result = cue;
  6041. break;
  6042. }
  6043. }
  6044. return result;
  6045. };
  6046. return TextTrackCueList;
  6047. }();
  6048. /**
  6049. * @file track-kinds.js
  6050. */
  6051. /**
  6052. * All possible `VideoTrackKind`s
  6053. *
  6054. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
  6055. * @typedef VideoTrack~Kind
  6056. * @enum
  6057. */
  6058. var VideoTrackKind = {
  6059. alternative: 'alternative',
  6060. captions: 'captions',
  6061. main: 'main',
  6062. sign: 'sign',
  6063. subtitles: 'subtitles',
  6064. commentary: 'commentary'
  6065. };
  6066. /**
  6067. * All possible `AudioTrackKind`s
  6068. *
  6069. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
  6070. * @typedef AudioTrack~Kind
  6071. * @enum
  6072. */
  6073. var AudioTrackKind = {
  6074. 'alternative': 'alternative',
  6075. 'descriptions': 'descriptions',
  6076. 'main': 'main',
  6077. 'main-desc': 'main-desc',
  6078. 'translation': 'translation',
  6079. 'commentary': 'commentary'
  6080. };
  6081. /**
  6082. * All possible `TextTrackKind`s
  6083. *
  6084. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
  6085. * @typedef TextTrack~Kind
  6086. * @enum
  6087. */
  6088. var TextTrackKind = {
  6089. subtitles: 'subtitles',
  6090. captions: 'captions',
  6091. descriptions: 'descriptions',
  6092. chapters: 'chapters',
  6093. metadata: 'metadata'
  6094. };
  6095. /**
  6096. * All possible `TextTrackMode`s
  6097. *
  6098. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
  6099. * @typedef TextTrack~Mode
  6100. * @enum
  6101. */
  6102. var TextTrackMode = {
  6103. disabled: 'disabled',
  6104. hidden: 'hidden',
  6105. showing: 'showing'
  6106. };
  6107. /**
  6108. * A Track class that contains all of the common functionality for {@link AudioTrack},
  6109. * {@link VideoTrack}, and {@link TextTrack}.
  6110. *
  6111. * > Note: This class should not be used directly
  6112. *
  6113. * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
  6114. * @extends EventTarget
  6115. * @abstract
  6116. */
  6117. var Track =
  6118. /*#__PURE__*/
  6119. function (_EventTarget) {
  6120. _inheritsLoose(Track, _EventTarget);
  6121. /**
  6122. * Create an instance of this class.
  6123. *
  6124. * @param {Object} [options={}]
  6125. * Object of option names and values
  6126. *
  6127. * @param {string} [options.kind='']
  6128. * A valid kind for the track type you are creating.
  6129. *
  6130. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6131. * A unique id for this AudioTrack.
  6132. *
  6133. * @param {string} [options.label='']
  6134. * The menu label for this track.
  6135. *
  6136. * @param {string} [options.language='']
  6137. * A valid two character language code.
  6138. *
  6139. * @abstract
  6140. */
  6141. function Track(options) {
  6142. var _this;
  6143. if (options === void 0) {
  6144. options = {};
  6145. }
  6146. _this = _EventTarget.call(this) || this;
  6147. var trackProps = {
  6148. id: options.id || 'vjs_track_' + newGUID(),
  6149. kind: options.kind || '',
  6150. label: options.label || '',
  6151. language: options.language || ''
  6152. };
  6153. /**
  6154. * @memberof Track
  6155. * @member {string} id
  6156. * The id of this track. Cannot be changed after creation.
  6157. * @instance
  6158. *
  6159. * @readonly
  6160. */
  6161. /**
  6162. * @memberof Track
  6163. * @member {string} kind
  6164. * The kind of track that this is. Cannot be changed after creation.
  6165. * @instance
  6166. *
  6167. * @readonly
  6168. */
  6169. /**
  6170. * @memberof Track
  6171. * @member {string} label
  6172. * The label of this track. Cannot be changed after creation.
  6173. * @instance
  6174. *
  6175. * @readonly
  6176. */
  6177. /**
  6178. * @memberof Track
  6179. * @member {string} language
  6180. * The two letter language code for this track. Cannot be changed after
  6181. * creation.
  6182. * @instance
  6183. *
  6184. * @readonly
  6185. */
  6186. var _loop = function _loop(key) {
  6187. Object.defineProperty(_assertThisInitialized(_this), key, {
  6188. get: function get() {
  6189. return trackProps[key];
  6190. },
  6191. set: function set() {}
  6192. });
  6193. };
  6194. for (var key in trackProps) {
  6195. _loop(key);
  6196. }
  6197. return _this;
  6198. }
  6199. return Track;
  6200. }(EventTarget);
  6201. /**
  6202. * @file url.js
  6203. * @module url
  6204. */
  6205. /**
  6206. * @typedef {Object} url:URLObject
  6207. *
  6208. * @property {string} protocol
  6209. * The protocol of the url that was parsed.
  6210. *
  6211. * @property {string} hostname
  6212. * The hostname of the url that was parsed.
  6213. *
  6214. * @property {string} port
  6215. * The port of the url that was parsed.
  6216. *
  6217. * @property {string} pathname
  6218. * The pathname of the url that was parsed.
  6219. *
  6220. * @property {string} search
  6221. * The search query of the url that was parsed.
  6222. *
  6223. * @property {string} hash
  6224. * The hash of the url that was parsed.
  6225. *
  6226. * @property {string} host
  6227. * The host of the url that was parsed.
  6228. */
  6229. /**
  6230. * Resolve and parse the elements of a URL.
  6231. *
  6232. * @function
  6233. * @param {String} url
  6234. * The url to parse
  6235. *
  6236. * @return {url:URLObject}
  6237. * An object of url details
  6238. */
  6239. var parseUrl = function parseUrl(url) {
  6240. var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
  6241. var a = document.createElement('a');
  6242. a.href = url; // IE8 (and 9?) Fix
  6243. // ie8 doesn't parse the URL correctly until the anchor is actually
  6244. // added to the body, and an innerHTML is needed to trigger the parsing
  6245. var addToBody = a.host === '' && a.protocol !== 'file:';
  6246. var div;
  6247. if (addToBody) {
  6248. div = document.createElement('div');
  6249. div.innerHTML = "<a href=\"" + url + "\"></a>";
  6250. a = div.firstChild; // prevent the div from affecting layout
  6251. div.setAttribute('style', 'display:none; position:absolute;');
  6252. document.body.appendChild(div);
  6253. } // Copy the specific URL properties to a new object
  6254. // This is also needed for IE8 because the anchor loses its
  6255. // properties when it's removed from the dom
  6256. var details = {};
  6257. for (var i = 0; i < props.length; i++) {
  6258. details[props[i]] = a[props[i]];
  6259. } // IE9 adds the port to the host property unlike everyone else. If
  6260. // a port identifier is added for standard ports, strip it.
  6261. if (details.protocol === 'http:') {
  6262. details.host = details.host.replace(/:80$/, '');
  6263. }
  6264. if (details.protocol === 'https:') {
  6265. details.host = details.host.replace(/:443$/, '');
  6266. }
  6267. if (!details.protocol) {
  6268. details.protocol = window$1.location.protocol;
  6269. }
  6270. if (addToBody) {
  6271. document.body.removeChild(div);
  6272. }
  6273. return details;
  6274. };
  6275. /**
  6276. * Get absolute version of relative URL. Used to tell Flash the correct URL.
  6277. *
  6278. * @function
  6279. * @param {string} url
  6280. * URL to make absolute
  6281. *
  6282. * @return {string}
  6283. * Absolute URL
  6284. *
  6285. * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  6286. */
  6287. var getAbsoluteURL = function getAbsoluteURL(url) {
  6288. // Check if absolute URL
  6289. if (!url.match(/^https?:\/\//)) {
  6290. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  6291. var div = document.createElement('div');
  6292. div.innerHTML = "<a href=\"" + url + "\">x</a>";
  6293. url = div.firstChild.href;
  6294. }
  6295. return url;
  6296. };
  6297. /**
  6298. * Returns the extension of the passed file name. It will return an empty string
  6299. * if passed an invalid path.
  6300. *
  6301. * @function
  6302. * @param {string} path
  6303. * The fileName path like '/path/to/file.mp4'
  6304. *
  6305. * @return {string}
  6306. * The extension in lower case or an empty string if no
  6307. * extension could be found.
  6308. */
  6309. var getFileExtension = function getFileExtension(path) {
  6310. if (typeof path === 'string') {
  6311. var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
  6312. var pathParts = splitPathRe.exec(path);
  6313. if (pathParts) {
  6314. return pathParts.pop().toLowerCase();
  6315. }
  6316. }
  6317. return '';
  6318. };
  6319. /**
  6320. * Returns whether the url passed is a cross domain request or not.
  6321. *
  6322. * @function
  6323. * @param {string} url
  6324. * The url to check.
  6325. *
  6326. * @return {boolean}
  6327. * Whether it is a cross domain request or not.
  6328. */
  6329. var isCrossOrigin = function isCrossOrigin(url) {
  6330. var winLoc = window$1.location;
  6331. var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
  6332. var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
  6333. // IE8 doesn't know location.origin, so we won't rely on it here
  6334. var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
  6335. return crossOrigin;
  6336. };
  6337. var Url = /*#__PURE__*/Object.freeze({
  6338. parseUrl: parseUrl,
  6339. getAbsoluteURL: getAbsoluteURL,
  6340. getFileExtension: getFileExtension,
  6341. isCrossOrigin: isCrossOrigin
  6342. });
  6343. var isFunction_1 = isFunction;
  6344. var toString$1 = Object.prototype.toString;
  6345. function isFunction(fn) {
  6346. var string = toString$1.call(fn);
  6347. return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below
  6348. fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
  6349. }
  6350. /* eslint no-invalid-this: 1 */
  6351. var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible ';
  6352. var slice = Array.prototype.slice;
  6353. var toStr = Object.prototype.toString;
  6354. var funcType = '[object Function]';
  6355. var implementation = function bind(that) {
  6356. var target = this;
  6357. if (typeof target !== 'function' || toStr.call(target) !== funcType) {
  6358. throw new TypeError(ERROR_MESSAGE + target);
  6359. }
  6360. var args = slice.call(arguments, 1);
  6361. var bound;
  6362. var binder = function binder() {
  6363. if (this instanceof bound) {
  6364. var result = target.apply(this, args.concat(slice.call(arguments)));
  6365. if (Object(result) === result) {
  6366. return result;
  6367. }
  6368. return this;
  6369. } else {
  6370. return target.apply(that, args.concat(slice.call(arguments)));
  6371. }
  6372. };
  6373. var boundLength = Math.max(0, target.length - args.length);
  6374. var boundArgs = [];
  6375. for (var i = 0; i < boundLength; i++) {
  6376. boundArgs.push('$' + i);
  6377. }
  6378. bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder);
  6379. if (target.prototype) {
  6380. var Empty = function Empty() {};
  6381. Empty.prototype = target.prototype;
  6382. bound.prototype = new Empty();
  6383. Empty.prototype = null;
  6384. }
  6385. return bound;
  6386. };
  6387. var functionBind = Function.prototype.bind || implementation;
  6388. var toStr$1 = Object.prototype.toString;
  6389. var isArguments = function isArguments(value) {
  6390. var str = toStr$1.call(value);
  6391. var isArgs = str === '[object Arguments]';
  6392. if (!isArgs) {
  6393. isArgs = str !== '[object Array]' && value !== null && typeof value === 'object' && typeof value.length === 'number' && value.length >= 0 && toStr$1.call(value.callee) === '[object Function]';
  6394. }
  6395. return isArgs;
  6396. };
  6397. var keysShim;
  6398. if (!Object.keys) {
  6399. // modified from https://github.com/es-shims/es5-shim
  6400. var has = Object.prototype.hasOwnProperty;
  6401. var toStr$2 = Object.prototype.toString;
  6402. var isArgs = isArguments; // eslint-disable-line global-require
  6403. var isEnumerable = Object.prototype.propertyIsEnumerable;
  6404. var hasDontEnumBug = !isEnumerable.call({
  6405. toString: null
  6406. }, 'toString');
  6407. var hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype');
  6408. var dontEnums = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor'];
  6409. var equalsConstructorPrototype = function equalsConstructorPrototype(o) {
  6410. var ctor = o.constructor;
  6411. return ctor && ctor.prototype === o;
  6412. };
  6413. var excludedKeys = {
  6414. $applicationCache: true,
  6415. $console: true,
  6416. $external: true,
  6417. $frame: true,
  6418. $frameElement: true,
  6419. $frames: true,
  6420. $innerHeight: true,
  6421. $innerWidth: true,
  6422. $onmozfullscreenchange: true,
  6423. $onmozfullscreenerror: true,
  6424. $outerHeight: true,
  6425. $outerWidth: true,
  6426. $pageXOffset: true,
  6427. $pageYOffset: true,
  6428. $parent: true,
  6429. $scrollLeft: true,
  6430. $scrollTop: true,
  6431. $scrollX: true,
  6432. $scrollY: true,
  6433. $self: true,
  6434. $webkitIndexedDB: true,
  6435. $webkitStorageInfo: true,
  6436. $window: true
  6437. };
  6438. var hasAutomationEqualityBug = function () {
  6439. /* global window */
  6440. if (typeof window === 'undefined') {
  6441. return false;
  6442. }
  6443. for (var k in window) {
  6444. try {
  6445. if (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') {
  6446. try {
  6447. equalsConstructorPrototype(window[k]);
  6448. } catch (e) {
  6449. return true;
  6450. }
  6451. }
  6452. } catch (e) {
  6453. return true;
  6454. }
  6455. }
  6456. return false;
  6457. }();
  6458. var equalsConstructorPrototypeIfNotBuggy = function equalsConstructorPrototypeIfNotBuggy(o) {
  6459. /* global window */
  6460. if (typeof window === 'undefined' || !hasAutomationEqualityBug) {
  6461. return equalsConstructorPrototype(o);
  6462. }
  6463. try {
  6464. return equalsConstructorPrototype(o);
  6465. } catch (e) {
  6466. return false;
  6467. }
  6468. };
  6469. keysShim = function keys(object) {
  6470. var isObject = object !== null && typeof object === 'object';
  6471. var isFunction = toStr$2.call(object) === '[object Function]';
  6472. var isArguments = isArgs(object);
  6473. var isString = isObject && toStr$2.call(object) === '[object String]';
  6474. var theKeys = [];
  6475. if (!isObject && !isFunction && !isArguments) {
  6476. throw new TypeError('Object.keys called on a non-object');
  6477. }
  6478. var skipProto = hasProtoEnumBug && isFunction;
  6479. if (isString && object.length > 0 && !has.call(object, 0)) {
  6480. for (var i = 0; i < object.length; ++i) {
  6481. theKeys.push(String(i));
  6482. }
  6483. }
  6484. if (isArguments && object.length > 0) {
  6485. for (var j = 0; j < object.length; ++j) {
  6486. theKeys.push(String(j));
  6487. }
  6488. } else {
  6489. for (var name in object) {
  6490. if (!(skipProto && name === 'prototype') && has.call(object, name)) {
  6491. theKeys.push(String(name));
  6492. }
  6493. }
  6494. }
  6495. if (hasDontEnumBug) {
  6496. var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);
  6497. for (var k = 0; k < dontEnums.length; ++k) {
  6498. if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) {
  6499. theKeys.push(dontEnums[k]);
  6500. }
  6501. }
  6502. }
  6503. return theKeys;
  6504. };
  6505. }
  6506. var implementation$1 = keysShim;
  6507. var slice$1 = Array.prototype.slice;
  6508. var origKeys = Object.keys;
  6509. var keysShim$1 = origKeys ? function keys(o) {
  6510. return origKeys(o);
  6511. } : implementation$1;
  6512. var originalKeys = Object.keys;
  6513. keysShim$1.shim = function shimObjectKeys() {
  6514. if (Object.keys) {
  6515. var keysWorksWithArguments = function () {
  6516. // Safari 5.0 bug
  6517. var args = Object.keys(arguments);
  6518. return args && args.length === arguments.length;
  6519. }(1, 2);
  6520. if (!keysWorksWithArguments) {
  6521. Object.keys = function keys(object) {
  6522. // eslint-disable-line func-name-matching
  6523. if (isArguments(object)) {
  6524. return originalKeys(slice$1.call(object));
  6525. }
  6526. return originalKeys(object);
  6527. };
  6528. }
  6529. } else {
  6530. Object.keys = keysShim$1;
  6531. }
  6532. return Object.keys || keysShim$1;
  6533. };
  6534. var objectKeys = keysShim$1;
  6535. var hasSymbols = typeof Symbol === 'function' && typeof Symbol('foo') === 'symbol';
  6536. var toStr$3 = Object.prototype.toString;
  6537. var concat = Array.prototype.concat;
  6538. var origDefineProperty = Object.defineProperty;
  6539. var isFunction$1 = function isFunction(fn) {
  6540. return typeof fn === 'function' && toStr$3.call(fn) === '[object Function]';
  6541. };
  6542. var arePropertyDescriptorsSupported = function arePropertyDescriptorsSupported() {
  6543. var obj = {};
  6544. try {
  6545. origDefineProperty(obj, 'x', {
  6546. enumerable: false,
  6547. value: obj
  6548. }); // eslint-disable-next-line no-unused-vars, no-restricted-syntax
  6549. for (var _ in obj) {
  6550. // jscs:ignore disallowUnusedVariables
  6551. return false;
  6552. }
  6553. return obj.x === obj;
  6554. } catch (e) {
  6555. /* this is IE 8. */
  6556. return false;
  6557. }
  6558. };
  6559. var supportsDescriptors = origDefineProperty && arePropertyDescriptorsSupported();
  6560. var defineProperty = function defineProperty(object, name, value, predicate) {
  6561. if (name in object && (!isFunction$1(predicate) || !predicate())) {
  6562. return;
  6563. }
  6564. if (supportsDescriptors) {
  6565. origDefineProperty(object, name, {
  6566. configurable: true,
  6567. enumerable: false,
  6568. value: value,
  6569. writable: true
  6570. });
  6571. } else {
  6572. object[name] = value;
  6573. }
  6574. };
  6575. var defineProperties = function defineProperties(object, map) {
  6576. var predicates = arguments.length > 2 ? arguments[2] : {};
  6577. var props = objectKeys(map);
  6578. if (hasSymbols) {
  6579. props = concat.call(props, Object.getOwnPropertySymbols(map));
  6580. }
  6581. for (var i = 0; i < props.length; i += 1) {
  6582. defineProperty(object, props[i], map[props[i]], predicates[props[i]]);
  6583. }
  6584. };
  6585. defineProperties.supportsDescriptors = !!supportsDescriptors;
  6586. var defineProperties_1 = defineProperties;
  6587. /* globals
  6588. Set,
  6589. Map,
  6590. WeakSet,
  6591. WeakMap,
  6592. Promise,
  6593. Symbol,
  6594. Proxy,
  6595. Atomics,
  6596. SharedArrayBuffer,
  6597. ArrayBuffer,
  6598. DataView,
  6599. Uint8Array,
  6600. Float32Array,
  6601. Float64Array,
  6602. Int8Array,
  6603. Int16Array,
  6604. Int32Array,
  6605. Uint8ClampedArray,
  6606. Uint16Array,
  6607. Uint32Array,
  6608. */
  6609. var undefined$1; // eslint-disable-line no-shadow-restricted-names
  6610. var ThrowTypeError = Object.getOwnPropertyDescriptor ? function () {
  6611. return Object.getOwnPropertyDescriptor(arguments, 'callee').get;
  6612. }() : function () {
  6613. throw new TypeError();
  6614. };
  6615. var hasSymbols$1 = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol';
  6616. var getProto = Object.getPrototypeOf || function (x) {
  6617. return x.__proto__;
  6618. }; // eslint-disable-line no-proto
  6619. var generatorFunction = undefined$1;
  6620. var asyncFunction = undefined$1;
  6621. var asyncGenFunction = undefined$1;
  6622. var TypedArray = typeof Uint8Array === 'undefined' ? undefined$1 : getProto(Uint8Array);
  6623. var INTRINSICS = {
  6624. '$ %Array%': Array,
  6625. '$ %ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined$1 : ArrayBuffer,
  6626. '$ %ArrayBufferPrototype%': typeof ArrayBuffer === 'undefined' ? undefined$1 : ArrayBuffer.prototype,
  6627. '$ %ArrayIteratorPrototype%': hasSymbols$1 ? getProto([][Symbol.iterator]()) : undefined$1,
  6628. '$ %ArrayPrototype%': Array.prototype,
  6629. '$ %ArrayProto_entries%': Array.prototype.entries,
  6630. '$ %ArrayProto_forEach%': Array.prototype.forEach,
  6631. '$ %ArrayProto_keys%': Array.prototype.keys,
  6632. '$ %ArrayProto_values%': Array.prototype.values,
  6633. '$ %AsyncFromSyncIteratorPrototype%': undefined$1,
  6634. '$ %AsyncFunction%': asyncFunction,
  6635. '$ %AsyncFunctionPrototype%': undefined$1,
  6636. '$ %AsyncGenerator%': undefined$1,
  6637. '$ %AsyncGeneratorFunction%': asyncGenFunction,
  6638. '$ %AsyncGeneratorPrototype%': undefined$1,
  6639. '$ %AsyncIteratorPrototype%': undefined$1,
  6640. '$ %Atomics%': typeof Atomics === 'undefined' ? undefined$1 : Atomics,
  6641. '$ %Boolean%': Boolean,
  6642. '$ %BooleanPrototype%': Boolean.prototype,
  6643. '$ %DataView%': typeof DataView === 'undefined' ? undefined$1 : DataView,
  6644. '$ %DataViewPrototype%': typeof DataView === 'undefined' ? undefined$1 : DataView.prototype,
  6645. '$ %Date%': Date,
  6646. '$ %DatePrototype%': Date.prototype,
  6647. '$ %decodeURI%': decodeURI,
  6648. '$ %decodeURIComponent%': decodeURIComponent,
  6649. '$ %encodeURI%': encodeURI,
  6650. '$ %encodeURIComponent%': encodeURIComponent,
  6651. '$ %Error%': Error,
  6652. '$ %ErrorPrototype%': Error.prototype,
  6653. '$ %eval%': eval,
  6654. // eslint-disable-line no-eval
  6655. '$ %EvalError%': EvalError,
  6656. '$ %EvalErrorPrototype%': EvalError.prototype,
  6657. '$ %Float32Array%': typeof Float32Array === 'undefined' ? undefined$1 : Float32Array,
  6658. '$ %Float32ArrayPrototype%': typeof Float32Array === 'undefined' ? undefined$1 : Float32Array.prototype,
  6659. '$ %Float64Array%': typeof Float64Array === 'undefined' ? undefined$1 : Float64Array,
  6660. '$ %Float64ArrayPrototype%': typeof Float64Array === 'undefined' ? undefined$1 : Float64Array.prototype,
  6661. '$ %Function%': Function,
  6662. '$ %FunctionPrototype%': Function.prototype,
  6663. '$ %Generator%': undefined$1,
  6664. '$ %GeneratorFunction%': generatorFunction,
  6665. '$ %GeneratorPrototype%': undefined$1,
  6666. '$ %Int8Array%': typeof Int8Array === 'undefined' ? undefined$1 : Int8Array,
  6667. '$ %Int8ArrayPrototype%': typeof Int8Array === 'undefined' ? undefined$1 : Int8Array.prototype,
  6668. '$ %Int16Array%': typeof Int16Array === 'undefined' ? undefined$1 : Int16Array,
  6669. '$ %Int16ArrayPrototype%': typeof Int16Array === 'undefined' ? undefined$1 : Int8Array.prototype,
  6670. '$ %Int32Array%': typeof Int32Array === 'undefined' ? undefined$1 : Int32Array,
  6671. '$ %Int32ArrayPrototype%': typeof Int32Array === 'undefined' ? undefined$1 : Int32Array.prototype,
  6672. '$ %isFinite%': isFinite,
  6673. '$ %isNaN%': isNaN,
  6674. '$ %IteratorPrototype%': hasSymbols$1 ? getProto(getProto([][Symbol.iterator]())) : undefined$1,
  6675. '$ %JSON%': JSON,
  6676. '$ %JSONParse%': JSON.parse,
  6677. '$ %Map%': typeof Map === 'undefined' ? undefined$1 : Map,
  6678. '$ %MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols$1 ? undefined$1 : getProto(new Map()[Symbol.iterator]()),
  6679. '$ %MapPrototype%': typeof Map === 'undefined' ? undefined$1 : Map.prototype,
  6680. '$ %Math%': Math,
  6681. '$ %Number%': Number,
  6682. '$ %NumberPrototype%': Number.prototype,
  6683. '$ %Object%': Object,
  6684. '$ %ObjectPrototype%': Object.prototype,
  6685. '$ %ObjProto_toString%': Object.prototype.toString,
  6686. '$ %ObjProto_valueOf%': Object.prototype.valueOf,
  6687. '$ %parseFloat%': parseFloat,
  6688. '$ %parseInt%': parseInt,
  6689. '$ %Promise%': typeof Promise === 'undefined' ? undefined$1 : Promise,
  6690. '$ %PromisePrototype%': typeof Promise === 'undefined' ? undefined$1 : Promise.prototype,
  6691. '$ %PromiseProto_then%': typeof Promise === 'undefined' ? undefined$1 : Promise.prototype.then,
  6692. '$ %Promise_all%': typeof Promise === 'undefined' ? undefined$1 : Promise.all,
  6693. '$ %Promise_reject%': typeof Promise === 'undefined' ? undefined$1 : Promise.reject,
  6694. '$ %Promise_resolve%': typeof Promise === 'undefined' ? undefined$1 : Promise.resolve,
  6695. '$ %Proxy%': typeof Proxy === 'undefined' ? undefined$1 : Proxy,
  6696. '$ %RangeError%': RangeError,
  6697. '$ %RangeErrorPrototype%': RangeError.prototype,
  6698. '$ %ReferenceError%': ReferenceError,
  6699. '$ %ReferenceErrorPrototype%': ReferenceError.prototype,
  6700. '$ %Reflect%': typeof Reflect === 'undefined' ? undefined$1 : Reflect,
  6701. '$ %RegExp%': RegExp,
  6702. '$ %RegExpPrototype%': RegExp.prototype,
  6703. '$ %Set%': typeof Set === 'undefined' ? undefined$1 : Set,
  6704. '$ %SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols$1 ? undefined$1 : getProto(new Set()[Symbol.iterator]()),
  6705. '$ %SetPrototype%': typeof Set === 'undefined' ? undefined$1 : Set.prototype,
  6706. '$ %SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined$1 : SharedArrayBuffer,
  6707. '$ %SharedArrayBufferPrototype%': typeof SharedArrayBuffer === 'undefined' ? undefined$1 : SharedArrayBuffer.prototype,
  6708. '$ %String%': String,
  6709. '$ %StringIteratorPrototype%': hasSymbols$1 ? getProto(''[Symbol.iterator]()) : undefined$1,
  6710. '$ %StringPrototype%': String.prototype,
  6711. '$ %Symbol%': hasSymbols$1 ? Symbol : undefined$1,
  6712. '$ %SymbolPrototype%': hasSymbols$1 ? Symbol.prototype : undefined$1,
  6713. '$ %SyntaxError%': SyntaxError,
  6714. '$ %SyntaxErrorPrototype%': SyntaxError.prototype,
  6715. '$ %ThrowTypeError%': ThrowTypeError,
  6716. '$ %TypedArray%': TypedArray,
  6717. '$ %TypedArrayPrototype%': TypedArray ? TypedArray.prototype : undefined$1,
  6718. '$ %TypeError%': TypeError,
  6719. '$ %TypeErrorPrototype%': TypeError.prototype,
  6720. '$ %Uint8Array%': typeof Uint8Array === 'undefined' ? undefined$1 : Uint8Array,
  6721. '$ %Uint8ArrayPrototype%': typeof Uint8Array === 'undefined' ? undefined$1 : Uint8Array.prototype,
  6722. '$ %Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined$1 : Uint8ClampedArray,
  6723. '$ %Uint8ClampedArrayPrototype%': typeof Uint8ClampedArray === 'undefined' ? undefined$1 : Uint8ClampedArray.prototype,
  6724. '$ %Uint16Array%': typeof Uint16Array === 'undefined' ? undefined$1 : Uint16Array,
  6725. '$ %Uint16ArrayPrototype%': typeof Uint16Array === 'undefined' ? undefined$1 : Uint16Array.prototype,
  6726. '$ %Uint32Array%': typeof Uint32Array === 'undefined' ? undefined$1 : Uint32Array,
  6727. '$ %Uint32ArrayPrototype%': typeof Uint32Array === 'undefined' ? undefined$1 : Uint32Array.prototype,
  6728. '$ %URIError%': URIError,
  6729. '$ %URIErrorPrototype%': URIError.prototype,
  6730. '$ %WeakMap%': typeof WeakMap === 'undefined' ? undefined$1 : WeakMap,
  6731. '$ %WeakMapPrototype%': typeof WeakMap === 'undefined' ? undefined$1 : WeakMap.prototype,
  6732. '$ %WeakSet%': typeof WeakSet === 'undefined' ? undefined$1 : WeakSet,
  6733. '$ %WeakSetPrototype%': typeof WeakSet === 'undefined' ? undefined$1 : WeakSet.prototype
  6734. };
  6735. var GetIntrinsic = function GetIntrinsic(name, allowMissing) {
  6736. if (arguments.length > 1 && typeof allowMissing !== 'boolean') {
  6737. throw new TypeError('"allowMissing" argument must be a boolean');
  6738. }
  6739. var key = '$ ' + name;
  6740. if (!(key in INTRINSICS)) {
  6741. throw new SyntaxError('intrinsic ' + name + ' does not exist!');
  6742. } // istanbul ignore if // hopefully this is impossible to test :-)
  6743. if (typeof INTRINSICS[key] === 'undefined' && !allowMissing) {
  6744. throw new TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!');
  6745. }
  6746. return INTRINSICS[key];
  6747. };
  6748. var src = functionBind.call(Function.call, Object.prototype.hasOwnProperty);
  6749. var $TypeError = GetIntrinsic('%TypeError%');
  6750. var $SyntaxError = GetIntrinsic('%SyntaxError%');
  6751. var predicates = {
  6752. // https://ecma-international.org/ecma-262/6.0/#sec-property-descriptor-specification-type
  6753. 'Property Descriptor': function isPropertyDescriptor(ES, Desc) {
  6754. if (ES.Type(Desc) !== 'Object') {
  6755. return false;
  6756. }
  6757. var allowed = {
  6758. '[[Configurable]]': true,
  6759. '[[Enumerable]]': true,
  6760. '[[Get]]': true,
  6761. '[[Set]]': true,
  6762. '[[Value]]': true,
  6763. '[[Writable]]': true
  6764. };
  6765. for (var key in Desc) {
  6766. // eslint-disable-line
  6767. if (src(Desc, key) && !allowed[key]) {
  6768. return false;
  6769. }
  6770. }
  6771. var isData = src(Desc, '[[Value]]');
  6772. var IsAccessor = src(Desc, '[[Get]]') || src(Desc, '[[Set]]');
  6773. if (isData && IsAccessor) {
  6774. throw new $TypeError('Property Descriptors may not be both accessor and data descriptors');
  6775. }
  6776. return true;
  6777. }
  6778. };
  6779. var assertRecord = function assertRecord(ES, recordType, argumentName, value) {
  6780. var predicate = predicates[recordType];
  6781. if (typeof predicate !== 'function') {
  6782. throw new $SyntaxError('unknown record type: ' + recordType);
  6783. }
  6784. if (!predicate(ES, value)) {
  6785. throw new $TypeError(argumentName + ' must be a ' + recordType);
  6786. }
  6787. console.log(predicate(ES, value), value);
  6788. };
  6789. var _isNaN = Number.isNaN || function isNaN(a) {
  6790. return a !== a;
  6791. };
  6792. var $isNaN = Number.isNaN || function (a) {
  6793. return a !== a;
  6794. };
  6795. var _isFinite = Number.isFinite || function (x) {
  6796. return typeof x === 'number' && !$isNaN(x) && x !== Infinity && x !== -Infinity;
  6797. };
  6798. var sign = function sign(number) {
  6799. return number >= 0 ? 1 : -1;
  6800. };
  6801. var mod = function mod(number, modulo) {
  6802. var remain = number % modulo;
  6803. return Math.floor(remain >= 0 ? remain : remain + modulo);
  6804. };
  6805. var fnToStr = Function.prototype.toString;
  6806. var constructorRegex = /^\s*class\b/;
  6807. var isES6ClassFn = function isES6ClassFunction(value) {
  6808. try {
  6809. var fnStr = fnToStr.call(value);
  6810. return constructorRegex.test(fnStr);
  6811. } catch (e) {
  6812. return false; // not a function
  6813. }
  6814. };
  6815. var tryFunctionObject = function tryFunctionToStr(value) {
  6816. try {
  6817. if (isES6ClassFn(value)) {
  6818. return false;
  6819. }
  6820. fnToStr.call(value);
  6821. return true;
  6822. } catch (e) {
  6823. return false;
  6824. }
  6825. };
  6826. var toStr$4 = Object.prototype.toString;
  6827. var fnClass = '[object Function]';
  6828. var genClass = '[object GeneratorFunction]';
  6829. var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
  6830. var isCallable = function isCallable(value) {
  6831. if (!value) {
  6832. return false;
  6833. }
  6834. if (typeof value !== 'function' && typeof value !== 'object') {
  6835. return false;
  6836. }
  6837. if (typeof value === 'function' && !value.prototype) {
  6838. return true;
  6839. }
  6840. if (hasToStringTag) {
  6841. return tryFunctionObject(value);
  6842. }
  6843. if (isES6ClassFn(value)) {
  6844. return false;
  6845. }
  6846. var strClass = toStr$4.call(value);
  6847. return strClass === fnClass || strClass === genClass;
  6848. };
  6849. var isPrimitive = function isPrimitive(value) {
  6850. return value === null || typeof value !== 'function' && typeof value !== 'object';
  6851. };
  6852. var toStr$5 = Object.prototype.toString; // http://ecma-international.org/ecma-262/5.1/#sec-8.12.8
  6853. var ES5internalSlots = {
  6854. '[[DefaultValue]]': function DefaultValue(O) {
  6855. var actualHint;
  6856. if (arguments.length > 1) {
  6857. actualHint = arguments[1];
  6858. } else {
  6859. actualHint = toStr$5.call(O) === '[object Date]' ? String : Number;
  6860. }
  6861. if (actualHint === String || actualHint === Number) {
  6862. var methods = actualHint === String ? ['toString', 'valueOf'] : ['valueOf', 'toString'];
  6863. var value, i;
  6864. for (i = 0; i < methods.length; ++i) {
  6865. if (isCallable(O[methods[i]])) {
  6866. value = O[methods[i]]();
  6867. if (isPrimitive(value)) {
  6868. return value;
  6869. }
  6870. }
  6871. }
  6872. throw new TypeError('No default value');
  6873. }
  6874. throw new TypeError('invalid [[DefaultValue]] hint supplied');
  6875. }
  6876. }; // http://ecma-international.org/ecma-262/5.1/#sec-9.1
  6877. var es5 = function ToPrimitive(input) {
  6878. if (isPrimitive(input)) {
  6879. return input;
  6880. }
  6881. if (arguments.length > 1) {
  6882. return ES5internalSlots['[[DefaultValue]]'](input, arguments[1]);
  6883. }
  6884. return ES5internalSlots['[[DefaultValue]]'](input);
  6885. };
  6886. var $Object = GetIntrinsic('%Object%');
  6887. var $TypeError$1 = GetIntrinsic('%TypeError%');
  6888. var $String = GetIntrinsic('%String%'); // https://es5.github.io/#x9
  6889. var ES5 = {
  6890. ToPrimitive: es5,
  6891. ToBoolean: function ToBoolean(value) {
  6892. return !!value;
  6893. },
  6894. ToNumber: function ToNumber(value) {
  6895. return +value; // eslint-disable-line no-implicit-coercion
  6896. },
  6897. ToInteger: function ToInteger(value) {
  6898. var number = this.ToNumber(value);
  6899. if (_isNaN(number)) {
  6900. return 0;
  6901. }
  6902. if (number === 0 || !_isFinite(number)) {
  6903. return number;
  6904. }
  6905. return sign(number) * Math.floor(Math.abs(number));
  6906. },
  6907. ToInt32: function ToInt32(x) {
  6908. return this.ToNumber(x) >> 0;
  6909. },
  6910. ToUint32: function ToUint32(x) {
  6911. return this.ToNumber(x) >>> 0;
  6912. },
  6913. ToUint16: function ToUint16(value) {
  6914. var number = this.ToNumber(value);
  6915. if (_isNaN(number) || number === 0 || !_isFinite(number)) {
  6916. return 0;
  6917. }
  6918. var posInt = sign(number) * Math.floor(Math.abs(number));
  6919. return mod(posInt, 0x10000);
  6920. },
  6921. ToString: function ToString(value) {
  6922. return $String(value);
  6923. },
  6924. ToObject: function ToObject(value) {
  6925. this.CheckObjectCoercible(value);
  6926. return $Object(value);
  6927. },
  6928. CheckObjectCoercible: function CheckObjectCoercible(value, optMessage) {
  6929. /* jshint eqnull:true */
  6930. if (value == null) {
  6931. throw new $TypeError$1(optMessage || 'Cannot call method on ' + value);
  6932. }
  6933. return value;
  6934. },
  6935. IsCallable: isCallable,
  6936. SameValue: function SameValue(x, y) {
  6937. if (x === y) {
  6938. // 0 === -0, but they are not identical.
  6939. if (x === 0) {
  6940. return 1 / x === 1 / y;
  6941. }
  6942. return true;
  6943. }
  6944. return _isNaN(x) && _isNaN(y);
  6945. },
  6946. // https://www.ecma-international.org/ecma-262/5.1/#sec-8
  6947. Type: function Type(x) {
  6948. if (x === null) {
  6949. return 'Null';
  6950. }
  6951. if (typeof x === 'undefined') {
  6952. return 'Undefined';
  6953. }
  6954. if (typeof x === 'function' || typeof x === 'object') {
  6955. return 'Object';
  6956. }
  6957. if (typeof x === 'number') {
  6958. return 'Number';
  6959. }
  6960. if (typeof x === 'boolean') {
  6961. return 'Boolean';
  6962. }
  6963. if (typeof x === 'string') {
  6964. return 'String';
  6965. }
  6966. },
  6967. // https://ecma-international.org/ecma-262/6.0/#sec-property-descriptor-specification-type
  6968. IsPropertyDescriptor: function IsPropertyDescriptor(Desc) {
  6969. if (this.Type(Desc) !== 'Object') {
  6970. return false;
  6971. }
  6972. var allowed = {
  6973. '[[Configurable]]': true,
  6974. '[[Enumerable]]': true,
  6975. '[[Get]]': true,
  6976. '[[Set]]': true,
  6977. '[[Value]]': true,
  6978. '[[Writable]]': true
  6979. };
  6980. for (var key in Desc) {
  6981. // eslint-disable-line
  6982. if (src(Desc, key) && !allowed[key]) {
  6983. return false;
  6984. }
  6985. }
  6986. var isData = src(Desc, '[[Value]]');
  6987. var IsAccessor = src(Desc, '[[Get]]') || src(Desc, '[[Set]]');
  6988. if (isData && IsAccessor) {
  6989. throw new $TypeError$1('Property Descriptors may not be both accessor and data descriptors');
  6990. }
  6991. return true;
  6992. },
  6993. // https://ecma-international.org/ecma-262/5.1/#sec-8.10.1
  6994. IsAccessorDescriptor: function IsAccessorDescriptor(Desc) {
  6995. if (typeof Desc === 'undefined') {
  6996. return false;
  6997. }
  6998. assertRecord(this, 'Property Descriptor', 'Desc', Desc);
  6999. if (!src(Desc, '[[Get]]') && !src(Desc, '[[Set]]')) {
  7000. return false;
  7001. }
  7002. return true;
  7003. },
  7004. // https://ecma-international.org/ecma-262/5.1/#sec-8.10.2
  7005. IsDataDescriptor: function IsDataDescriptor(Desc) {
  7006. if (typeof Desc === 'undefined') {
  7007. return false;
  7008. }
  7009. assertRecord(this, 'Property Descriptor', 'Desc', Desc);
  7010. if (!src(Desc, '[[Value]]') && !src(Desc, '[[Writable]]')) {
  7011. return false;
  7012. }
  7013. return true;
  7014. },
  7015. // https://ecma-international.org/ecma-262/5.1/#sec-8.10.3
  7016. IsGenericDescriptor: function IsGenericDescriptor(Desc) {
  7017. if (typeof Desc === 'undefined') {
  7018. return false;
  7019. }
  7020. assertRecord(this, 'Property Descriptor', 'Desc', Desc);
  7021. if (!this.IsAccessorDescriptor(Desc) && !this.IsDataDescriptor(Desc)) {
  7022. return true;
  7023. }
  7024. return false;
  7025. },
  7026. // https://ecma-international.org/ecma-262/5.1/#sec-8.10.4
  7027. FromPropertyDescriptor: function FromPropertyDescriptor(Desc) {
  7028. if (typeof Desc === 'undefined') {
  7029. return Desc;
  7030. }
  7031. assertRecord(this, 'Property Descriptor', 'Desc', Desc);
  7032. if (this.IsDataDescriptor(Desc)) {
  7033. return {
  7034. value: Desc['[[Value]]'],
  7035. writable: !!Desc['[[Writable]]'],
  7036. enumerable: !!Desc['[[Enumerable]]'],
  7037. configurable: !!Desc['[[Configurable]]']
  7038. };
  7039. } else if (this.IsAccessorDescriptor(Desc)) {
  7040. return {
  7041. get: Desc['[[Get]]'],
  7042. set: Desc['[[Set]]'],
  7043. enumerable: !!Desc['[[Enumerable]]'],
  7044. configurable: !!Desc['[[Configurable]]']
  7045. };
  7046. } else {
  7047. throw new $TypeError$1('FromPropertyDescriptor must be called with a fully populated Property Descriptor');
  7048. }
  7049. },
  7050. // https://ecma-international.org/ecma-262/5.1/#sec-8.10.5
  7051. ToPropertyDescriptor: function ToPropertyDescriptor(Obj) {
  7052. if (this.Type(Obj) !== 'Object') {
  7053. throw new $TypeError$1('ToPropertyDescriptor requires an object');
  7054. }
  7055. var desc = {};
  7056. if (src(Obj, 'enumerable')) {
  7057. desc['[[Enumerable]]'] = this.ToBoolean(Obj.enumerable);
  7058. }
  7059. if (src(Obj, 'configurable')) {
  7060. desc['[[Configurable]]'] = this.ToBoolean(Obj.configurable);
  7061. }
  7062. if (src(Obj, 'value')) {
  7063. desc['[[Value]]'] = Obj.value;
  7064. }
  7065. if (src(Obj, 'writable')) {
  7066. desc['[[Writable]]'] = this.ToBoolean(Obj.writable);
  7067. }
  7068. if (src(Obj, 'get')) {
  7069. var getter = Obj.get;
  7070. if (typeof getter !== 'undefined' && !this.IsCallable(getter)) {
  7071. throw new TypeError('getter must be a function');
  7072. }
  7073. desc['[[Get]]'] = getter;
  7074. }
  7075. if (src(Obj, 'set')) {
  7076. var setter = Obj.set;
  7077. if (typeof setter !== 'undefined' && !this.IsCallable(setter)) {
  7078. throw new $TypeError$1('setter must be a function');
  7079. }
  7080. desc['[[Set]]'] = setter;
  7081. }
  7082. if ((src(desc, '[[Get]]') || src(desc, '[[Set]]')) && (src(desc, '[[Value]]') || src(desc, '[[Writable]]'))) {
  7083. throw new $TypeError$1('Invalid property descriptor. Cannot both specify accessors and a value or writable attribute');
  7084. }
  7085. return desc;
  7086. }
  7087. };
  7088. var es5$1 = ES5;
  7089. var replace = functionBind.call(Function.call, String.prototype.replace);
  7090. var leftWhitespace = /^[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+/;
  7091. var rightWhitespace = /[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+$/;
  7092. var implementation$2 = function trim() {
  7093. var S = es5$1.ToString(es5$1.CheckObjectCoercible(this));
  7094. return replace(replace(S, leftWhitespace, ''), rightWhitespace, '');
  7095. };
  7096. var zeroWidthSpace = "\u200B";
  7097. var polyfill = function getPolyfill() {
  7098. if (String.prototype.trim && zeroWidthSpace.trim() === zeroWidthSpace) {
  7099. return String.prototype.trim;
  7100. }
  7101. return implementation$2;
  7102. };
  7103. var shim = function shimStringTrim() {
  7104. var polyfill$1 = polyfill();
  7105. defineProperties_1(String.prototype, {
  7106. trim: polyfill$1
  7107. }, {
  7108. trim: function trim() {
  7109. return String.prototype.trim !== polyfill$1;
  7110. }
  7111. });
  7112. return polyfill$1;
  7113. };
  7114. var boundTrim = functionBind.call(Function.call, polyfill());
  7115. defineProperties_1(boundTrim, {
  7116. getPolyfill: polyfill,
  7117. implementation: implementation$2,
  7118. shim: shim
  7119. });
  7120. var string_prototype_trim = boundTrim;
  7121. var toStr$6 = Object.prototype.toString;
  7122. var hasOwnProperty = Object.prototype.hasOwnProperty;
  7123. var forEachArray = function forEachArray(array, iterator, receiver) {
  7124. for (var i = 0, len = array.length; i < len; i++) {
  7125. if (hasOwnProperty.call(array, i)) {
  7126. if (receiver == null) {
  7127. iterator(array[i], i, array);
  7128. } else {
  7129. iterator.call(receiver, array[i], i, array);
  7130. }
  7131. }
  7132. }
  7133. };
  7134. var forEachString = function forEachString(string, iterator, receiver) {
  7135. for (var i = 0, len = string.length; i < len; i++) {
  7136. // no such thing as a sparse string.
  7137. if (receiver == null) {
  7138. iterator(string.charAt(i), i, string);
  7139. } else {
  7140. iterator.call(receiver, string.charAt(i), i, string);
  7141. }
  7142. }
  7143. };
  7144. var forEachObject = function forEachObject(object, iterator, receiver) {
  7145. for (var k in object) {
  7146. if (hasOwnProperty.call(object, k)) {
  7147. if (receiver == null) {
  7148. iterator(object[k], k, object);
  7149. } else {
  7150. iterator.call(receiver, object[k], k, object);
  7151. }
  7152. }
  7153. }
  7154. };
  7155. var forEach = function forEach(list, iterator, thisArg) {
  7156. if (!isCallable(iterator)) {
  7157. throw new TypeError('iterator must be a function');
  7158. }
  7159. var receiver;
  7160. if (arguments.length >= 3) {
  7161. receiver = thisArg;
  7162. }
  7163. if (toStr$6.call(list) === '[object Array]') {
  7164. forEachArray(list, iterator, receiver);
  7165. } else if (typeof list === 'string') {
  7166. forEachString(list, iterator, receiver);
  7167. } else {
  7168. forEachObject(list, iterator, receiver);
  7169. }
  7170. };
  7171. var forEach_1 = forEach;
  7172. var isArray = function isArray(arg) {
  7173. return Object.prototype.toString.call(arg) === '[object Array]';
  7174. };
  7175. var parseHeaders = function parseHeaders(headers) {
  7176. if (!headers) return {};
  7177. var result = {};
  7178. forEach_1(string_prototype_trim(headers).split('\n'), function (row) {
  7179. var index = row.indexOf(':'),
  7180. key = string_prototype_trim(row.slice(0, index)).toLowerCase(),
  7181. value = string_prototype_trim(row.slice(index + 1));
  7182. if (typeof result[key] === 'undefined') {
  7183. result[key] = value;
  7184. } else if (isArray(result[key])) {
  7185. result[key].push(value);
  7186. } else {
  7187. result[key] = [result[key], value];
  7188. }
  7189. });
  7190. return result;
  7191. };
  7192. var immutable = extend;
  7193. var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
  7194. function extend() {
  7195. var target = {};
  7196. for (var i = 0; i < arguments.length; i++) {
  7197. var source = arguments[i];
  7198. for (var key in source) {
  7199. if (hasOwnProperty$1.call(source, key)) {
  7200. target[key] = source[key];
  7201. }
  7202. }
  7203. }
  7204. return target;
  7205. }
  7206. var xhr = createXHR;
  7207. createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop;
  7208. createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest;
  7209. forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) {
  7210. createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
  7211. options = initParams(uri, options, callback);
  7212. options.method = method.toUpperCase();
  7213. return _createXHR(options);
  7214. };
  7215. });
  7216. function forEachArray$1(array, iterator) {
  7217. for (var i = 0; i < array.length; i++) {
  7218. iterator(array[i]);
  7219. }
  7220. }
  7221. function isEmpty(obj) {
  7222. for (var i in obj) {
  7223. if (obj.hasOwnProperty(i)) return false;
  7224. }
  7225. return true;
  7226. }
  7227. function initParams(uri, options, callback) {
  7228. var params = uri;
  7229. if (isFunction_1(options)) {
  7230. callback = options;
  7231. if (typeof uri === "string") {
  7232. params = {
  7233. uri: uri
  7234. };
  7235. }
  7236. } else {
  7237. params = immutable(options, {
  7238. uri: uri
  7239. });
  7240. }
  7241. params.callback = callback;
  7242. return params;
  7243. }
  7244. function createXHR(uri, options, callback) {
  7245. options = initParams(uri, options, callback);
  7246. return _createXHR(options);
  7247. }
  7248. function _createXHR(options) {
  7249. if (typeof options.callback === "undefined") {
  7250. throw new Error("callback argument missing");
  7251. }
  7252. var called = false;
  7253. var callback = function cbOnce(err, response, body) {
  7254. if (!called) {
  7255. called = true;
  7256. options.callback(err, response, body);
  7257. }
  7258. };
  7259. function readystatechange() {
  7260. if (xhr.readyState === 4) {
  7261. setTimeout(loadFunc, 0);
  7262. }
  7263. }
  7264. function getBody() {
  7265. // Chrome with requestType=blob throws errors arround when even testing access to responseText
  7266. var body = undefined;
  7267. if (xhr.response) {
  7268. body = xhr.response;
  7269. } else {
  7270. body = xhr.responseText || getXml(xhr);
  7271. }
  7272. if (isJson) {
  7273. try {
  7274. body = JSON.parse(body);
  7275. } catch (e) {}
  7276. }
  7277. return body;
  7278. }
  7279. function errorFunc(evt) {
  7280. clearTimeout(timeoutTimer);
  7281. if (!(evt instanceof Error)) {
  7282. evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
  7283. }
  7284. evt.statusCode = 0;
  7285. return callback(evt, failureResponse);
  7286. } // will load the data & process the response in a special response object
  7287. function loadFunc() {
  7288. if (aborted) return;
  7289. var status;
  7290. clearTimeout(timeoutTimer);
  7291. if (options.useXDR && xhr.status === undefined) {
  7292. //IE8 CORS GET successful response doesn't have a status field, but body is fine
  7293. status = 200;
  7294. } else {
  7295. status = xhr.status === 1223 ? 204 : xhr.status;
  7296. }
  7297. var response = failureResponse;
  7298. var err = null;
  7299. if (status !== 0) {
  7300. response = {
  7301. body: getBody(),
  7302. statusCode: status,
  7303. method: method,
  7304. headers: {},
  7305. url: uri,
  7306. rawRequest: xhr
  7307. };
  7308. if (xhr.getAllResponseHeaders) {
  7309. //remember xhr can in fact be XDR for CORS in IE
  7310. response.headers = parseHeaders(xhr.getAllResponseHeaders());
  7311. }
  7312. } else {
  7313. err = new Error("Internal XMLHttpRequest Error");
  7314. }
  7315. return callback(err, response, response.body);
  7316. }
  7317. var xhr = options.xhr || null;
  7318. if (!xhr) {
  7319. if (options.cors || options.useXDR) {
  7320. xhr = new createXHR.XDomainRequest();
  7321. } else {
  7322. xhr = new createXHR.XMLHttpRequest();
  7323. }
  7324. }
  7325. var key;
  7326. var aborted;
  7327. var uri = xhr.url = options.uri || options.url;
  7328. var method = xhr.method = options.method || "GET";
  7329. var body = options.body || options.data;
  7330. var headers = xhr.headers = options.headers || {};
  7331. var sync = !!options.sync;
  7332. var isJson = false;
  7333. var timeoutTimer;
  7334. var failureResponse = {
  7335. body: undefined,
  7336. headers: {},
  7337. statusCode: 0,
  7338. method: method,
  7339. url: uri,
  7340. rawRequest: xhr
  7341. };
  7342. if ("json" in options && options.json !== false) {
  7343. isJson = true;
  7344. headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
  7345. if (method !== "GET" && method !== "HEAD") {
  7346. headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
  7347. body = JSON.stringify(options.json === true ? body : options.json);
  7348. }
  7349. }
  7350. xhr.onreadystatechange = readystatechange;
  7351. xhr.onload = loadFunc;
  7352. xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
  7353. xhr.onprogress = function () {// IE must die
  7354. };
  7355. xhr.onabort = function () {
  7356. aborted = true;
  7357. };
  7358. xhr.ontimeout = errorFunc;
  7359. xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
  7360. if (!sync) {
  7361. xhr.withCredentials = !!options.withCredentials;
  7362. } // Cannot set timeout with sync request
  7363. // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
  7364. // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
  7365. if (!sync && options.timeout > 0) {
  7366. timeoutTimer = setTimeout(function () {
  7367. if (aborted) return;
  7368. aborted = true; //IE9 may still call readystatechange
  7369. xhr.abort("timeout");
  7370. var e = new Error("XMLHttpRequest timeout");
  7371. e.code = "ETIMEDOUT";
  7372. errorFunc(e);
  7373. }, options.timeout);
  7374. }
  7375. if (xhr.setRequestHeader) {
  7376. for (key in headers) {
  7377. if (headers.hasOwnProperty(key)) {
  7378. xhr.setRequestHeader(key, headers[key]);
  7379. }
  7380. }
  7381. } else if (options.headers && !isEmpty(options.headers)) {
  7382. throw new Error("Headers cannot be set on an XDomainRequest object");
  7383. }
  7384. if ("responseType" in options) {
  7385. xhr.responseType = options.responseType;
  7386. }
  7387. if ("beforeSend" in options && typeof options.beforeSend === "function") {
  7388. options.beforeSend(xhr);
  7389. } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
  7390. // XMLHttpRequest spec says to pass null as body to indicate no body
  7391. // See https://github.com/naugtur/xhr/issues/100.
  7392. xhr.send(body || null);
  7393. return xhr;
  7394. }
  7395. function getXml(xhr) {
  7396. if (xhr.responseType === "document") {
  7397. return xhr.responseXML;
  7398. }
  7399. var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
  7400. if (xhr.responseType === "" && !firefoxBugTakenEffect) {
  7401. return xhr.responseXML;
  7402. }
  7403. return null;
  7404. }
  7405. function noop() {}
  7406. /**
  7407. * Takes a webvtt file contents and parses it into cues
  7408. *
  7409. * @param {string} srcContent
  7410. * webVTT file contents
  7411. *
  7412. * @param {TextTrack} track
  7413. * TextTrack to add cues to. Cues come from the srcContent.
  7414. *
  7415. * @private
  7416. */
  7417. var parseCues = function parseCues(srcContent, track) {
  7418. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
  7419. var errors = [];
  7420. parser.oncue = function (cue) {
  7421. track.addCue(cue);
  7422. };
  7423. parser.onparsingerror = function (error) {
  7424. errors.push(error);
  7425. };
  7426. parser.onflush = function () {
  7427. track.trigger({
  7428. type: 'loadeddata',
  7429. target: track
  7430. });
  7431. };
  7432. parser.parse(srcContent);
  7433. if (errors.length > 0) {
  7434. if (window$1.console && window$1.console.groupCollapsed) {
  7435. window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
  7436. }
  7437. errors.forEach(function (error) {
  7438. return log.error(error);
  7439. });
  7440. if (window$1.console && window$1.console.groupEnd) {
  7441. window$1.console.groupEnd();
  7442. }
  7443. }
  7444. parser.flush();
  7445. };
  7446. /**
  7447. * Load a `TextTrack` from a specified url.
  7448. *
  7449. * @param {string} src
  7450. * Url to load track from.
  7451. *
  7452. * @param {TextTrack} track
  7453. * Track to add cues to. Comes from the content at the end of `url`.
  7454. *
  7455. * @private
  7456. */
  7457. var loadTrack = function loadTrack(src, track) {
  7458. var opts = {
  7459. uri: src
  7460. };
  7461. var crossOrigin = isCrossOrigin(src);
  7462. if (crossOrigin) {
  7463. opts.cors = crossOrigin;
  7464. }
  7465. xhr(opts, bind(this, function (err, response, responseBody) {
  7466. if (err) {
  7467. return log.error(err, response);
  7468. }
  7469. track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  7470. // NOTE: this is only used for the alt/video.novtt.js build
  7471. if (typeof window$1.WebVTT !== 'function') {
  7472. if (track.tech_) {
  7473. // to prevent use before define eslint error, we define loadHandler
  7474. // as a let here
  7475. var loadHandler;
  7476. var errorHandler = function errorHandler() {
  7477. log.error("vttjs failed to load, stopping trying to process " + track.src);
  7478. track.tech_.off('vttjsloaded', loadHandler);
  7479. };
  7480. loadHandler = function loadHandler() {
  7481. track.tech_.off('vttjserror', errorHandler);
  7482. return parseCues(responseBody, track);
  7483. };
  7484. track.tech_.one('vttjsloaded', loadHandler);
  7485. track.tech_.one('vttjserror', errorHandler);
  7486. }
  7487. } else {
  7488. parseCues(responseBody, track);
  7489. }
  7490. }));
  7491. };
  7492. /**
  7493. * A representation of a single `TextTrack`.
  7494. *
  7495. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
  7496. * @extends Track
  7497. */
  7498. var TextTrack =
  7499. /*#__PURE__*/
  7500. function (_Track) {
  7501. _inheritsLoose(TextTrack, _Track);
  7502. /**
  7503. * Create an instance of this class.
  7504. *
  7505. * @param {Object} options={}
  7506. * Object of option names and values
  7507. *
  7508. * @param {Tech} options.tech
  7509. * A reference to the tech that owns this TextTrack.
  7510. *
  7511. * @param {TextTrack~Kind} [options.kind='subtitles']
  7512. * A valid text track kind.
  7513. *
  7514. * @param {TextTrack~Mode} [options.mode='disabled']
  7515. * A valid text track mode.
  7516. *
  7517. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7518. * A unique id for this TextTrack.
  7519. *
  7520. * @param {string} [options.label='']
  7521. * The menu label for this track.
  7522. *
  7523. * @param {string} [options.language='']
  7524. * A valid two character language code.
  7525. *
  7526. * @param {string} [options.srclang='']
  7527. * A valid two character language code. An alternative, but deprioritized
  7528. * version of `options.language`
  7529. *
  7530. * @param {string} [options.src]
  7531. * A url to TextTrack cues.
  7532. *
  7533. * @param {boolean} [options.default]
  7534. * If this track should default to on or off.
  7535. */
  7536. function TextTrack(options) {
  7537. var _this;
  7538. if (options === void 0) {
  7539. options = {};
  7540. }
  7541. if (!options.tech) {
  7542. throw new Error('A tech was not provided.');
  7543. }
  7544. var settings = mergeOptions(options, {
  7545. kind: TextTrackKind[options.kind] || 'subtitles',
  7546. language: options.language || options.srclang || ''
  7547. });
  7548. var mode = TextTrackMode[settings.mode] || 'disabled';
  7549. var default_ = settings["default"];
  7550. if (settings.kind === 'metadata' || settings.kind === 'chapters') {
  7551. mode = 'hidden';
  7552. }
  7553. _this = _Track.call(this, settings) || this;
  7554. _this.tech_ = settings.tech;
  7555. _this.cues_ = [];
  7556. _this.activeCues_ = [];
  7557. var cues = new TextTrackCueList(_this.cues_);
  7558. var activeCues = new TextTrackCueList(_this.activeCues_);
  7559. var changed = false;
  7560. var timeupdateHandler = bind(_assertThisInitialized(_this), function () {
  7561. // Accessing this.activeCues for the side-effects of updating itself
  7562. // due to its nature as a getter function. Do not remove or cues will
  7563. // stop updating!
  7564. // Use the setter to prevent deletion from uglify (pure_getters rule)
  7565. this.activeCues = this.activeCues;
  7566. if (changed) {
  7567. this.trigger('cuechange');
  7568. changed = false;
  7569. }
  7570. });
  7571. if (mode !== 'disabled') {
  7572. _this.tech_.ready(function () {
  7573. _this.tech_.on('timeupdate', timeupdateHandler);
  7574. }, true);
  7575. }
  7576. Object.defineProperties(_assertThisInitialized(_this), {
  7577. /**
  7578. * @memberof TextTrack
  7579. * @member {boolean} default
  7580. * If this track was set to be on or off by default. Cannot be changed after
  7581. * creation.
  7582. * @instance
  7583. *
  7584. * @readonly
  7585. */
  7586. "default": {
  7587. get: function get() {
  7588. return default_;
  7589. },
  7590. set: function set() {}
  7591. },
  7592. /**
  7593. * @memberof TextTrack
  7594. * @member {string} mode
  7595. * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
  7596. * not be set if setting to an invalid mode.
  7597. * @instance
  7598. *
  7599. * @fires TextTrack#modechange
  7600. */
  7601. mode: {
  7602. get: function get() {
  7603. return mode;
  7604. },
  7605. set: function set(newMode) {
  7606. var _this2 = this;
  7607. if (!TextTrackMode[newMode]) {
  7608. return;
  7609. }
  7610. mode = newMode;
  7611. if (mode !== 'disabled') {
  7612. this.tech_.ready(function () {
  7613. _this2.tech_.on('timeupdate', timeupdateHandler);
  7614. }, true);
  7615. } else {
  7616. this.tech_.off('timeupdate', timeupdateHandler);
  7617. }
  7618. /**
  7619. * An event that fires when mode changes on this track. This allows
  7620. * the TextTrackList that holds this track to act accordingly.
  7621. *
  7622. * > Note: This is not part of the spec!
  7623. *
  7624. * @event TextTrack#modechange
  7625. * @type {EventTarget~Event}
  7626. */
  7627. this.trigger('modechange');
  7628. }
  7629. },
  7630. /**
  7631. * @memberof TextTrack
  7632. * @member {TextTrackCueList} cues
  7633. * The text track cue list for this TextTrack.
  7634. * @instance
  7635. */
  7636. cues: {
  7637. get: function get() {
  7638. if (!this.loaded_) {
  7639. return null;
  7640. }
  7641. return cues;
  7642. },
  7643. set: function set() {}
  7644. },
  7645. /**
  7646. * @memberof TextTrack
  7647. * @member {TextTrackCueList} activeCues
  7648. * The list text track cues that are currently active for this TextTrack.
  7649. * @instance
  7650. */
  7651. activeCues: {
  7652. get: function get() {
  7653. if (!this.loaded_) {
  7654. return null;
  7655. } // nothing to do
  7656. if (this.cues.length === 0) {
  7657. return activeCues;
  7658. }
  7659. var ct = this.tech_.currentTime();
  7660. var active = [];
  7661. for (var i = 0, l = this.cues.length; i < l; i++) {
  7662. var cue = this.cues[i];
  7663. if (cue.startTime <= ct && cue.endTime >= ct) {
  7664. active.push(cue);
  7665. } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
  7666. active.push(cue);
  7667. }
  7668. }
  7669. changed = false;
  7670. if (active.length !== this.activeCues_.length) {
  7671. changed = true;
  7672. } else {
  7673. for (var _i = 0; _i < active.length; _i++) {
  7674. if (this.activeCues_.indexOf(active[_i]) === -1) {
  7675. changed = true;
  7676. }
  7677. }
  7678. }
  7679. this.activeCues_ = active;
  7680. activeCues.setCues_(this.activeCues_);
  7681. return activeCues;
  7682. },
  7683. // /!\ Keep this setter empty (see the timeupdate handler above)
  7684. set: function set() {}
  7685. }
  7686. });
  7687. if (settings.src) {
  7688. _this.src = settings.src;
  7689. loadTrack(settings.src, _assertThisInitialized(_this));
  7690. } else {
  7691. _this.loaded_ = true;
  7692. }
  7693. return _this;
  7694. }
  7695. /**
  7696. * Add a cue to the internal list of cues.
  7697. *
  7698. * @param {TextTrack~Cue} cue
  7699. * The cue to add to our internal list
  7700. */
  7701. var _proto = TextTrack.prototype;
  7702. _proto.addCue = function addCue(originalCue) {
  7703. var cue = originalCue;
  7704. if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
  7705. cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
  7706. for (var prop in originalCue) {
  7707. if (!(prop in cue)) {
  7708. cue[prop] = originalCue[prop];
  7709. }
  7710. } // make sure that `id` is copied over
  7711. cue.id = originalCue.id;
  7712. cue.originalCue_ = originalCue;
  7713. }
  7714. var tracks = this.tech_.textTracks();
  7715. for (var i = 0; i < tracks.length; i++) {
  7716. if (tracks[i] !== this) {
  7717. tracks[i].removeCue(cue);
  7718. }
  7719. }
  7720. this.cues_.push(cue);
  7721. this.cues.setCues_(this.cues_);
  7722. }
  7723. /**
  7724. * Remove a cue from our internal list
  7725. *
  7726. * @param {TextTrack~Cue} removeCue
  7727. * The cue to remove from our internal list
  7728. */
  7729. ;
  7730. _proto.removeCue = function removeCue(_removeCue) {
  7731. var i = this.cues_.length;
  7732. while (i--) {
  7733. var cue = this.cues_[i];
  7734. if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
  7735. this.cues_.splice(i, 1);
  7736. this.cues.setCues_(this.cues_);
  7737. break;
  7738. }
  7739. }
  7740. };
  7741. return TextTrack;
  7742. }(Track);
  7743. /**
  7744. * cuechange - One or more cues in the track have become active or stopped being active.
  7745. */
  7746. TextTrack.prototype.allowedEvents_ = {
  7747. cuechange: 'cuechange'
  7748. };
  7749. /**
  7750. * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
  7751. * only one `AudioTrack` in the list will be enabled at a time.
  7752. *
  7753. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
  7754. * @extends Track
  7755. */
  7756. var AudioTrack =
  7757. /*#__PURE__*/
  7758. function (_Track) {
  7759. _inheritsLoose(AudioTrack, _Track);
  7760. /**
  7761. * Create an instance of this class.
  7762. *
  7763. * @param {Object} [options={}]
  7764. * Object of option names and values
  7765. *
  7766. * @param {AudioTrack~Kind} [options.kind='']
  7767. * A valid audio track kind
  7768. *
  7769. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7770. * A unique id for this AudioTrack.
  7771. *
  7772. * @param {string} [options.label='']
  7773. * The menu label for this track.
  7774. *
  7775. * @param {string} [options.language='']
  7776. * A valid two character language code.
  7777. *
  7778. * @param {boolean} [options.enabled]
  7779. * If this track is the one that is currently playing. If this track is part of
  7780. * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
  7781. */
  7782. function AudioTrack(options) {
  7783. var _this;
  7784. if (options === void 0) {
  7785. options = {};
  7786. }
  7787. var settings = mergeOptions(options, {
  7788. kind: AudioTrackKind[options.kind] || ''
  7789. });
  7790. _this = _Track.call(this, settings) || this;
  7791. var enabled = false;
  7792. /**
  7793. * @memberof AudioTrack
  7794. * @member {boolean} enabled
  7795. * If this `AudioTrack` is enabled or not. When setting this will
  7796. * fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
  7797. * @instance
  7798. *
  7799. * @fires VideoTrack#selectedchange
  7800. */
  7801. Object.defineProperty(_assertThisInitialized(_this), 'enabled', {
  7802. get: function get() {
  7803. return enabled;
  7804. },
  7805. set: function set(newEnabled) {
  7806. // an invalid or unchanged value
  7807. if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
  7808. return;
  7809. }
  7810. enabled = newEnabled;
  7811. /**
  7812. * An event that fires when enabled changes on this track. This allows
  7813. * the AudioTrackList that holds this track to act accordingly.
  7814. *
  7815. * > Note: This is not part of the spec! Native tracks will do
  7816. * this internally without an event.
  7817. *
  7818. * @event AudioTrack#enabledchange
  7819. * @type {EventTarget~Event}
  7820. */
  7821. this.trigger('enabledchange');
  7822. }
  7823. }); // if the user sets this track to selected then
  7824. // set selected to that true value otherwise
  7825. // we keep it false
  7826. if (settings.enabled) {
  7827. _this.enabled = settings.enabled;
  7828. }
  7829. _this.loaded_ = true;
  7830. return _this;
  7831. }
  7832. return AudioTrack;
  7833. }(Track);
  7834. /**
  7835. * A representation of a single `VideoTrack`.
  7836. *
  7837. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
  7838. * @extends Track
  7839. */
  7840. var VideoTrack =
  7841. /*#__PURE__*/
  7842. function (_Track) {
  7843. _inheritsLoose(VideoTrack, _Track);
  7844. /**
  7845. * Create an instance of this class.
  7846. *
  7847. * @param {Object} [options={}]
  7848. * Object of option names and values
  7849. *
  7850. * @param {string} [options.kind='']
  7851. * A valid {@link VideoTrack~Kind}
  7852. *
  7853. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7854. * A unique id for this AudioTrack.
  7855. *
  7856. * @param {string} [options.label='']
  7857. * The menu label for this track.
  7858. *
  7859. * @param {string} [options.language='']
  7860. * A valid two character language code.
  7861. *
  7862. * @param {boolean} [options.selected]
  7863. * If this track is the one that is currently playing.
  7864. */
  7865. function VideoTrack(options) {
  7866. var _this;
  7867. if (options === void 0) {
  7868. options = {};
  7869. }
  7870. var settings = mergeOptions(options, {
  7871. kind: VideoTrackKind[options.kind] || ''
  7872. });
  7873. _this = _Track.call(this, settings) || this;
  7874. var selected = false;
  7875. /**
  7876. * @memberof VideoTrack
  7877. * @member {boolean} selected
  7878. * If this `VideoTrack` is selected or not. When setting this will
  7879. * fire {@link VideoTrack#selectedchange} if the state of selected changed.
  7880. * @instance
  7881. *
  7882. * @fires VideoTrack#selectedchange
  7883. */
  7884. Object.defineProperty(_assertThisInitialized(_this), 'selected', {
  7885. get: function get() {
  7886. return selected;
  7887. },
  7888. set: function set(newSelected) {
  7889. // an invalid or unchanged value
  7890. if (typeof newSelected !== 'boolean' || newSelected === selected) {
  7891. return;
  7892. }
  7893. selected = newSelected;
  7894. /**
  7895. * An event that fires when selected changes on this track. This allows
  7896. * the VideoTrackList that holds this track to act accordingly.
  7897. *
  7898. * > Note: This is not part of the spec! Native tracks will do
  7899. * this internally without an event.
  7900. *
  7901. * @event VideoTrack#selectedchange
  7902. * @type {EventTarget~Event}
  7903. */
  7904. this.trigger('selectedchange');
  7905. }
  7906. }); // if the user sets this track to selected then
  7907. // set selected to that true value otherwise
  7908. // we keep it false
  7909. if (settings.selected) {
  7910. _this.selected = settings.selected;
  7911. }
  7912. return _this;
  7913. }
  7914. return VideoTrack;
  7915. }(Track);
  7916. /**
  7917. * @memberof HTMLTrackElement
  7918. * @typedef {HTMLTrackElement~ReadyState}
  7919. * @enum {number}
  7920. */
  7921. var NONE = 0;
  7922. var LOADING = 1;
  7923. var LOADED = 2;
  7924. var ERROR = 3;
  7925. /**
  7926. * A single track represented in the DOM.
  7927. *
  7928. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
  7929. * @extends EventTarget
  7930. */
  7931. var HTMLTrackElement =
  7932. /*#__PURE__*/
  7933. function (_EventTarget) {
  7934. _inheritsLoose(HTMLTrackElement, _EventTarget);
  7935. /**
  7936. * Create an instance of this class.
  7937. *
  7938. * @param {Object} options={}
  7939. * Object of option names and values
  7940. *
  7941. * @param {Tech} options.tech
  7942. * A reference to the tech that owns this HTMLTrackElement.
  7943. *
  7944. * @param {TextTrack~Kind} [options.kind='subtitles']
  7945. * A valid text track kind.
  7946. *
  7947. * @param {TextTrack~Mode} [options.mode='disabled']
  7948. * A valid text track mode.
  7949. *
  7950. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7951. * A unique id for this TextTrack.
  7952. *
  7953. * @param {string} [options.label='']
  7954. * The menu label for this track.
  7955. *
  7956. * @param {string} [options.language='']
  7957. * A valid two character language code.
  7958. *
  7959. * @param {string} [options.srclang='']
  7960. * A valid two character language code. An alternative, but deprioritized
  7961. * vesion of `options.language`
  7962. *
  7963. * @param {string} [options.src]
  7964. * A url to TextTrack cues.
  7965. *
  7966. * @param {boolean} [options.default]
  7967. * If this track should default to on or off.
  7968. */
  7969. function HTMLTrackElement(options) {
  7970. var _this;
  7971. if (options === void 0) {
  7972. options = {};
  7973. }
  7974. _this = _EventTarget.call(this) || this;
  7975. var readyState;
  7976. var track = new TextTrack(options);
  7977. _this.kind = track.kind;
  7978. _this.src = track.src;
  7979. _this.srclang = track.language;
  7980. _this.label = track.label;
  7981. _this["default"] = track["default"];
  7982. Object.defineProperties(_assertThisInitialized(_this), {
  7983. /**
  7984. * @memberof HTMLTrackElement
  7985. * @member {HTMLTrackElement~ReadyState} readyState
  7986. * The current ready state of the track element.
  7987. * @instance
  7988. */
  7989. readyState: {
  7990. get: function get() {
  7991. return readyState;
  7992. }
  7993. },
  7994. /**
  7995. * @memberof HTMLTrackElement
  7996. * @member {TextTrack} track
  7997. * The underlying TextTrack object.
  7998. * @instance
  7999. *
  8000. */
  8001. track: {
  8002. get: function get() {
  8003. return track;
  8004. }
  8005. }
  8006. });
  8007. readyState = NONE;
  8008. /**
  8009. * @listens TextTrack#loadeddata
  8010. * @fires HTMLTrackElement#load
  8011. */
  8012. track.addEventListener('loadeddata', function () {
  8013. readyState = LOADED;
  8014. _this.trigger({
  8015. type: 'load',
  8016. target: _assertThisInitialized(_this)
  8017. });
  8018. });
  8019. return _this;
  8020. }
  8021. return HTMLTrackElement;
  8022. }(EventTarget);
  8023. HTMLTrackElement.prototype.allowedEvents_ = {
  8024. load: 'load'
  8025. };
  8026. HTMLTrackElement.NONE = NONE;
  8027. HTMLTrackElement.LOADING = LOADING;
  8028. HTMLTrackElement.LOADED = LOADED;
  8029. HTMLTrackElement.ERROR = ERROR;
  8030. /*
  8031. * This file contains all track properties that are used in
  8032. * player.js, tech.js, html5.js and possibly other techs in the future.
  8033. */
  8034. var NORMAL = {
  8035. audio: {
  8036. ListClass: AudioTrackList,
  8037. TrackClass: AudioTrack,
  8038. capitalName: 'Audio'
  8039. },
  8040. video: {
  8041. ListClass: VideoTrackList,
  8042. TrackClass: VideoTrack,
  8043. capitalName: 'Video'
  8044. },
  8045. text: {
  8046. ListClass: TextTrackList,
  8047. TrackClass: TextTrack,
  8048. capitalName: 'Text'
  8049. }
  8050. };
  8051. Object.keys(NORMAL).forEach(function (type) {
  8052. NORMAL[type].getterName = type + "Tracks";
  8053. NORMAL[type].privateName = type + "Tracks_";
  8054. });
  8055. var REMOTE = {
  8056. remoteText: {
  8057. ListClass: TextTrackList,
  8058. TrackClass: TextTrack,
  8059. capitalName: 'RemoteText',
  8060. getterName: 'remoteTextTracks',
  8061. privateName: 'remoteTextTracks_'
  8062. },
  8063. remoteTextEl: {
  8064. ListClass: HtmlTrackElementList,
  8065. TrackClass: HTMLTrackElement,
  8066. capitalName: 'RemoteTextTrackEls',
  8067. getterName: 'remoteTextTrackEls',
  8068. privateName: 'remoteTextTrackEls_'
  8069. }
  8070. };
  8071. var ALL = mergeOptions(NORMAL, REMOTE);
  8072. REMOTE.names = Object.keys(REMOTE);
  8073. NORMAL.names = Object.keys(NORMAL);
  8074. ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
  8075. /**
  8076. * Copyright 2013 vtt.js Contributors
  8077. *
  8078. * Licensed under the Apache License, Version 2.0 (the "License");
  8079. * you may not use this file except in compliance with the License.
  8080. * You may obtain a copy of the License at
  8081. *
  8082. * http://www.apache.org/licenses/LICENSE-2.0
  8083. *
  8084. * Unless required by applicable law or agreed to in writing, software
  8085. * distributed under the License is distributed on an "AS IS" BASIS,
  8086. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8087. * See the License for the specific language governing permissions and
  8088. * limitations under the License.
  8089. */
  8090. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  8091. /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
  8092. var _objCreate = Object.create || function () {
  8093. function F() {}
  8094. return function (o) {
  8095. if (arguments.length !== 1) {
  8096. throw new Error('Object.create shim only accepts one parameter.');
  8097. }
  8098. F.prototype = o;
  8099. return new F();
  8100. };
  8101. }(); // Creates a new ParserError object from an errorData object. The errorData
  8102. // object should have default code and message properties. The default message
  8103. // property can be overriden by passing in a message parameter.
  8104. // See ParsingError.Errors below for acceptable errors.
  8105. function ParsingError(errorData, message) {
  8106. this.name = "ParsingError";
  8107. this.code = errorData.code;
  8108. this.message = message || errorData.message;
  8109. }
  8110. ParsingError.prototype = _objCreate(Error.prototype);
  8111. ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors.
  8112. ParsingError.Errors = {
  8113. BadSignature: {
  8114. code: 0,
  8115. message: "Malformed WebVTT signature."
  8116. },
  8117. BadTimeStamp: {
  8118. code: 1,
  8119. message: "Malformed time stamp."
  8120. }
  8121. }; // Try to parse input as a time stamp.
  8122. function parseTimeStamp(input) {
  8123. function computeSeconds(h, m, s, f) {
  8124. return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
  8125. }
  8126. var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
  8127. if (!m) {
  8128. return null;
  8129. }
  8130. if (m[3]) {
  8131. // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
  8132. return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
  8133. } else if (m[1] > 59) {
  8134. // Timestamp takes the form of [hours]:[minutes].[milliseconds]
  8135. // First position is hours as it's over 59.
  8136. return computeSeconds(m[1], m[2], 0, m[4]);
  8137. } else {
  8138. // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
  8139. return computeSeconds(0, m[1], m[2], m[4]);
  8140. }
  8141. } // A settings object holds key/value pairs and will ignore anything but the first
  8142. // assignment to a specific key.
  8143. function Settings() {
  8144. this.values = _objCreate(null);
  8145. }
  8146. Settings.prototype = {
  8147. // Only accept the first assignment to any key.
  8148. set: function set(k, v) {
  8149. if (!this.get(k) && v !== "") {
  8150. this.values[k] = v;
  8151. }
  8152. },
  8153. // Return the value for a key, or a default value.
  8154. // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
  8155. // a number of possible default values as properties where 'defaultKey' is
  8156. // the key of the property that will be chosen; otherwise it's assumed to be
  8157. // a single value.
  8158. get: function get(k, dflt, defaultKey) {
  8159. if (defaultKey) {
  8160. return this.has(k) ? this.values[k] : dflt[defaultKey];
  8161. }
  8162. return this.has(k) ? this.values[k] : dflt;
  8163. },
  8164. // Check whether we have a value for a key.
  8165. has: function has(k) {
  8166. return k in this.values;
  8167. },
  8168. // Accept a setting if its one of the given alternatives.
  8169. alt: function alt(k, v, a) {
  8170. for (var n = 0; n < a.length; ++n) {
  8171. if (v === a[n]) {
  8172. this.set(k, v);
  8173. break;
  8174. }
  8175. }
  8176. },
  8177. // Accept a setting if its a valid (signed) integer.
  8178. integer: function integer(k, v) {
  8179. if (/^-?\d+$/.test(v)) {
  8180. // integer
  8181. this.set(k, parseInt(v, 10));
  8182. }
  8183. },
  8184. // Accept a setting if its a valid percentage.
  8185. percent: function percent(k, v) {
  8186. var m;
  8187. if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
  8188. v = parseFloat(v);
  8189. if (v >= 0 && v <= 100) {
  8190. this.set(k, v);
  8191. return true;
  8192. }
  8193. }
  8194. return false;
  8195. }
  8196. }; // Helper function to parse input into groups separated by 'groupDelim', and
  8197. // interprete each group as a key/value pair separated by 'keyValueDelim'.
  8198. function parseOptions(input, callback, keyValueDelim, groupDelim) {
  8199. var groups = groupDelim ? input.split(groupDelim) : [input];
  8200. for (var i in groups) {
  8201. if (typeof groups[i] !== "string") {
  8202. continue;
  8203. }
  8204. var kv = groups[i].split(keyValueDelim);
  8205. if (kv.length !== 2) {
  8206. continue;
  8207. }
  8208. var k = kv[0];
  8209. var v = kv[1];
  8210. callback(k, v);
  8211. }
  8212. }
  8213. function parseCue(input, cue, regionList) {
  8214. // Remember the original input if we need to throw an error.
  8215. var oInput = input; // 4.1 WebVTT timestamp
  8216. function consumeTimeStamp() {
  8217. var ts = parseTimeStamp(input);
  8218. if (ts === null) {
  8219. throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
  8220. } // Remove time stamp from input.
  8221. input = input.replace(/^[^\sa-zA-Z-]+/, "");
  8222. return ts;
  8223. } // 4.4.2 WebVTT cue settings
  8224. function consumeCueSettings(input, cue) {
  8225. var settings = new Settings();
  8226. parseOptions(input, function (k, v) {
  8227. switch (k) {
  8228. case "region":
  8229. // Find the last region we parsed with the same region id.
  8230. for (var i = regionList.length - 1; i >= 0; i--) {
  8231. if (regionList[i].id === v) {
  8232. settings.set(k, regionList[i].region);
  8233. break;
  8234. }
  8235. }
  8236. break;
  8237. case "vertical":
  8238. settings.alt(k, v, ["rl", "lr"]);
  8239. break;
  8240. case "line":
  8241. var vals = v.split(","),
  8242. vals0 = vals[0];
  8243. settings.integer(k, vals0);
  8244. settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
  8245. settings.alt(k, vals0, ["auto"]);
  8246. if (vals.length === 2) {
  8247. settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
  8248. }
  8249. break;
  8250. case "position":
  8251. vals = v.split(",");
  8252. settings.percent(k, vals[0]);
  8253. if (vals.length === 2) {
  8254. settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
  8255. }
  8256. break;
  8257. case "size":
  8258. settings.percent(k, v);
  8259. break;
  8260. case "align":
  8261. settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
  8262. break;
  8263. }
  8264. }, /:/, /\s/); // Apply default values for any missing fields.
  8265. cue.region = settings.get("region", null);
  8266. cue.vertical = settings.get("vertical", "");
  8267. cue.line = settings.get("line", "auto");
  8268. cue.lineAlign = settings.get("lineAlign", "start");
  8269. cue.snapToLines = settings.get("snapToLines", true);
  8270. cue.size = settings.get("size", 100);
  8271. cue.align = settings.get("align", "middle");
  8272. cue.position = settings.get("position", {
  8273. start: 0,
  8274. left: 0,
  8275. middle: 50,
  8276. end: 100,
  8277. right: 100
  8278. }, cue.align);
  8279. cue.positionAlign = settings.get("positionAlign", {
  8280. start: "start",
  8281. left: "start",
  8282. middle: "middle",
  8283. end: "end",
  8284. right: "end"
  8285. }, cue.align);
  8286. }
  8287. function skipWhitespace() {
  8288. input = input.replace(/^\s+/, "");
  8289. } // 4.1 WebVTT cue timings.
  8290. skipWhitespace();
  8291. cue.startTime = consumeTimeStamp(); // (1) collect cue start time
  8292. skipWhitespace();
  8293. if (input.substr(0, 3) !== "-->") {
  8294. // (3) next characters must match "-->"
  8295. throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
  8296. }
  8297. input = input.substr(3);
  8298. skipWhitespace();
  8299. cue.endTime = consumeTimeStamp(); // (5) collect cue end time
  8300. // 4.1 WebVTT cue settings list.
  8301. skipWhitespace();
  8302. consumeCueSettings(input, cue);
  8303. }
  8304. var ESCAPE = {
  8305. "&amp;": "&",
  8306. "&lt;": "<",
  8307. "&gt;": ">",
  8308. "&lrm;": "\u200E",
  8309. "&rlm;": "\u200F",
  8310. "&nbsp;": "\xA0"
  8311. };
  8312. var TAG_NAME = {
  8313. c: "span",
  8314. i: "i",
  8315. b: "b",
  8316. u: "u",
  8317. ruby: "ruby",
  8318. rt: "rt",
  8319. v: "span",
  8320. lang: "span"
  8321. };
  8322. var TAG_ANNOTATION = {
  8323. v: "title",
  8324. lang: "lang"
  8325. };
  8326. var NEEDS_PARENT = {
  8327. rt: "ruby"
  8328. }; // Parse content into a document fragment.
  8329. function parseContent(window, input) {
  8330. function nextToken() {
  8331. // Check for end-of-string.
  8332. if (!input) {
  8333. return null;
  8334. } // Consume 'n' characters from the input.
  8335. function consume(result) {
  8336. input = input.substr(result.length);
  8337. return result;
  8338. }
  8339. var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return
  8340. // the tag.
  8341. return consume(m[1] ? m[1] : m[2]);
  8342. } // Unescape a string 's'.
  8343. function unescape1(e) {
  8344. return ESCAPE[e];
  8345. }
  8346. function unescape(s) {
  8347. while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) {
  8348. s = s.replace(m[0], unescape1);
  8349. }
  8350. return s;
  8351. }
  8352. function shouldAdd(current, element) {
  8353. return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
  8354. } // Create an element for this tag.
  8355. function createElement(type, annotation) {
  8356. var tagName = TAG_NAME[type];
  8357. if (!tagName) {
  8358. return null;
  8359. }
  8360. var element = window.document.createElement(tagName);
  8361. element.localName = tagName;
  8362. var name = TAG_ANNOTATION[type];
  8363. if (name && annotation) {
  8364. element[name] = annotation.trim();
  8365. }
  8366. return element;
  8367. }
  8368. var rootDiv = window.document.createElement("div"),
  8369. current = rootDiv,
  8370. t,
  8371. tagStack = [];
  8372. while ((t = nextToken()) !== null) {
  8373. if (t[0] === '<') {
  8374. if (t[1] === "/") {
  8375. // If the closing tag matches, move back up to the parent node.
  8376. if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
  8377. tagStack.pop();
  8378. current = current.parentNode;
  8379. } // Otherwise just ignore the end tag.
  8380. continue;
  8381. }
  8382. var ts = parseTimeStamp(t.substr(1, t.length - 2));
  8383. var node;
  8384. if (ts) {
  8385. // Timestamps are lead nodes as well.
  8386. node = window.document.createProcessingInstruction("timestamp", ts);
  8387. current.appendChild(node);
  8388. continue;
  8389. }
  8390. var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag.
  8391. if (!m) {
  8392. continue;
  8393. } // Try to construct an element, and ignore the tag if we couldn't.
  8394. node = createElement(m[1], m[3]);
  8395. if (!node) {
  8396. continue;
  8397. } // Determine if the tag should be added based on the context of where it
  8398. // is placed in the cuetext.
  8399. if (!shouldAdd(current, node)) {
  8400. continue;
  8401. } // Set the class list (as a list of classes, separated by space).
  8402. if (m[2]) {
  8403. node.className = m[2].substr(1).replace('.', ' ');
  8404. } // Append the node to the current node, and enter the scope of the new
  8405. // node.
  8406. tagStack.push(m[1]);
  8407. current.appendChild(node);
  8408. current = node;
  8409. continue;
  8410. } // Text nodes are leaf nodes.
  8411. current.appendChild(window.document.createTextNode(unescape(t)));
  8412. }
  8413. return rootDiv;
  8414. } // This is a list of all the Unicode characters that have a strong
  8415. // right-to-left category. What this means is that these characters are
  8416. // written right-to-left for sure. It was generated by pulling all the strong
  8417. // right-to-left characters out of the Unicode data table. That table can
  8418. // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
  8419. 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]];
  8420. function isStrongRTLChar(charCode) {
  8421. for (var i = 0; i < strongRTLRanges.length; i++) {
  8422. var currentRange = strongRTLRanges[i];
  8423. if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
  8424. return true;
  8425. }
  8426. }
  8427. return false;
  8428. }
  8429. function determineBidi(cueDiv) {
  8430. var nodeStack = [],
  8431. text = "",
  8432. charCode;
  8433. if (!cueDiv || !cueDiv.childNodes) {
  8434. return "ltr";
  8435. }
  8436. function pushNodes(nodeStack, node) {
  8437. for (var i = node.childNodes.length - 1; i >= 0; i--) {
  8438. nodeStack.push(node.childNodes[i]);
  8439. }
  8440. }
  8441. function nextTextNode(nodeStack) {
  8442. if (!nodeStack || !nodeStack.length) {
  8443. return null;
  8444. }
  8445. var node = nodeStack.pop(),
  8446. text = node.textContent || node.innerText;
  8447. if (text) {
  8448. // TODO: This should match all unicode type B characters (paragraph
  8449. // separator characters). See issue #115.
  8450. var m = text.match(/^.*(\n|\r)/);
  8451. if (m) {
  8452. nodeStack.length = 0;
  8453. return m[0];
  8454. }
  8455. return text;
  8456. }
  8457. if (node.tagName === "ruby") {
  8458. return nextTextNode(nodeStack);
  8459. }
  8460. if (node.childNodes) {
  8461. pushNodes(nodeStack, node);
  8462. return nextTextNode(nodeStack);
  8463. }
  8464. }
  8465. pushNodes(nodeStack, cueDiv);
  8466. while (text = nextTextNode(nodeStack)) {
  8467. for (var i = 0; i < text.length; i++) {
  8468. charCode = text.charCodeAt(i);
  8469. if (isStrongRTLChar(charCode)) {
  8470. return "rtl";
  8471. }
  8472. }
  8473. }
  8474. return "ltr";
  8475. }
  8476. function computeLinePos(cue) {
  8477. if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
  8478. return cue.line;
  8479. }
  8480. if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
  8481. return -1;
  8482. }
  8483. var track = cue.track,
  8484. trackList = track.textTrackList,
  8485. count = 0;
  8486. for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
  8487. if (trackList[i].mode === "showing") {
  8488. count++;
  8489. }
  8490. }
  8491. return ++count * -1;
  8492. }
  8493. function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the
  8494. // div on 'this'.
  8495. StyleBox.prototype.applyStyles = function (styles, div) {
  8496. div = div || this.div;
  8497. for (var prop in styles) {
  8498. if (styles.hasOwnProperty(prop)) {
  8499. div.style[prop] = styles[prop];
  8500. }
  8501. }
  8502. };
  8503. StyleBox.prototype.formatStyle = function (val, unit) {
  8504. return val === 0 ? 0 : val + unit;
  8505. }; // Constructs the computed display state of the cue (a div). Places the div
  8506. // into the overlay which should be a block level element (usually a div).
  8507. function CueStyleBox(window, cue, styleOptions) {
  8508. StyleBox.call(this);
  8509. this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
  8510. // have inline positioning and will function as the cue background box.
  8511. this.cueDiv = parseContent(window, cue.text);
  8512. var styles = {
  8513. color: "rgba(255, 255, 255, 1)",
  8514. backgroundColor: "rgba(0, 0, 0, 0.8)",
  8515. position: "relative",
  8516. left: 0,
  8517. right: 0,
  8518. top: 0,
  8519. bottom: 0,
  8520. display: "inline",
  8521. writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
  8522. unicodeBidi: "plaintext"
  8523. };
  8524. this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue
  8525. // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
  8526. // mirrors of them except "middle" which is "center" in CSS.
  8527. this.div = window.document.createElement("div");
  8528. styles = {
  8529. direction: determineBidi(this.cueDiv),
  8530. writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
  8531. unicodeBidi: "plaintext",
  8532. textAlign: cue.align === "middle" ? "center" : cue.align,
  8533. font: styleOptions.font,
  8534. whiteSpace: "pre-line",
  8535. position: "absolute"
  8536. };
  8537. this.applyStyles(styles);
  8538. this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text
  8539. // position of the cue box. The reference edge will be resolved later when
  8540. // the box orientation styles are applied.
  8541. var textPos = 0;
  8542. switch (cue.positionAlign) {
  8543. case "start":
  8544. textPos = cue.position;
  8545. break;
  8546. case "middle":
  8547. textPos = cue.position - cue.size / 2;
  8548. break;
  8549. case "end":
  8550. textPos = cue.position - cue.size;
  8551. break;
  8552. } // Horizontal box orientation; textPos is the distance from the left edge of the
  8553. // area to the left edge of the box and cue.size is the distance extending to
  8554. // the right from there.
  8555. if (cue.vertical === "") {
  8556. this.applyStyles({
  8557. left: this.formatStyle(textPos, "%"),
  8558. width: this.formatStyle(cue.size, "%")
  8559. }); // Vertical box orientation; textPos is the distance from the top edge of the
  8560. // area to the top edge of the box and cue.size is the height extending
  8561. // downwards from there.
  8562. } else {
  8563. this.applyStyles({
  8564. top: this.formatStyle(textPos, "%"),
  8565. height: this.formatStyle(cue.size, "%")
  8566. });
  8567. }
  8568. this.move = function (box) {
  8569. this.applyStyles({
  8570. top: this.formatStyle(box.top, "px"),
  8571. bottom: this.formatStyle(box.bottom, "px"),
  8572. left: this.formatStyle(box.left, "px"),
  8573. right: this.formatStyle(box.right, "px"),
  8574. height: this.formatStyle(box.height, "px"),
  8575. width: this.formatStyle(box.width, "px")
  8576. });
  8577. };
  8578. }
  8579. CueStyleBox.prototype = _objCreate(StyleBox.prototype);
  8580. CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily
  8581. // compute things with such as if it overlaps or intersects with another Element.
  8582. // Can initialize it with either a StyleBox or another BoxPosition.
  8583. function BoxPosition(obj) {
  8584. // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
  8585. // was passed in and we need to copy the results of 'getBoundingClientRect'
  8586. // as the object returned is readonly. All co-ordinate values are in reference
  8587. // to the viewport origin (top left).
  8588. var lh, height, width, top;
  8589. if (obj.div) {
  8590. height = obj.div.offsetHeight;
  8591. width = obj.div.offsetWidth;
  8592. top = obj.div.offsetTop;
  8593. var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
  8594. obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of
  8595. // the inner div's lines. This could be due to bold text, etc, on some platforms.
  8596. // In this case we should get the average line height and use that. This will
  8597. // result in the desired behaviour.
  8598. lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
  8599. }
  8600. this.left = obj.left;
  8601. this.right = obj.right;
  8602. this.top = obj.top || top;
  8603. this.height = obj.height || height;
  8604. this.bottom = obj.bottom || top + (obj.height || height);
  8605. this.width = obj.width || width;
  8606. this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
  8607. } // Move the box along a particular axis. Optionally pass in an amount to move
  8608. // the box. If no amount is passed then the default is the line height of the
  8609. // box.
  8610. BoxPosition.prototype.move = function (axis, toMove) {
  8611. toMove = toMove !== undefined ? toMove : this.lineHeight;
  8612. switch (axis) {
  8613. case "+x":
  8614. this.left += toMove;
  8615. this.right += toMove;
  8616. break;
  8617. case "-x":
  8618. this.left -= toMove;
  8619. this.right -= toMove;
  8620. break;
  8621. case "+y":
  8622. this.top += toMove;
  8623. this.bottom += toMove;
  8624. break;
  8625. case "-y":
  8626. this.top -= toMove;
  8627. this.bottom -= toMove;
  8628. break;
  8629. }
  8630. }; // Check if this box overlaps another box, b2.
  8631. BoxPosition.prototype.overlaps = function (b2) {
  8632. return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
  8633. }; // Check if this box overlaps any other boxes in boxes.
  8634. BoxPosition.prototype.overlapsAny = function (boxes) {
  8635. for (var i = 0; i < boxes.length; i++) {
  8636. if (this.overlaps(boxes[i])) {
  8637. return true;
  8638. }
  8639. }
  8640. return false;
  8641. }; // Check if this box is within another box.
  8642. BoxPosition.prototype.within = function (container) {
  8643. return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
  8644. }; // Check if this box is entirely within the container or it is overlapping
  8645. // on the edge opposite of the axis direction passed. For example, if "+x" is
  8646. // passed and the box is overlapping on the left edge of the container, then
  8647. // return true.
  8648. BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
  8649. switch (axis) {
  8650. case "+x":
  8651. return this.left < container.left;
  8652. case "-x":
  8653. return this.right > container.right;
  8654. case "+y":
  8655. return this.top < container.top;
  8656. case "-y":
  8657. return this.bottom > container.bottom;
  8658. }
  8659. }; // Find the percentage of the area that this box is overlapping with another
  8660. // box.
  8661. BoxPosition.prototype.intersectPercentage = function (b2) {
  8662. var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
  8663. y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
  8664. intersectArea = x * y;
  8665. return intersectArea / (this.height * this.width);
  8666. }; // Convert the positions from this box to CSS compatible positions using
  8667. // the reference container's positions. This has to be done because this
  8668. // box's positions are in reference to the viewport origin, whereas, CSS
  8669. // values are in referecne to their respective edges.
  8670. BoxPosition.prototype.toCSSCompatValues = function (reference) {
  8671. return {
  8672. top: this.top - reference.top,
  8673. bottom: reference.bottom - this.bottom,
  8674. left: this.left - reference.left,
  8675. right: reference.right - this.right,
  8676. height: this.height,
  8677. width: this.width
  8678. };
  8679. }; // Get an object that represents the box's position without anything extra.
  8680. // Can pass a StyleBox, HTMLElement, or another BoxPositon.
  8681. BoxPosition.getSimpleBoxPosition = function (obj) {
  8682. var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
  8683. var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
  8684. var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
  8685. obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
  8686. var ret = {
  8687. left: obj.left,
  8688. right: obj.right,
  8689. top: obj.top || top,
  8690. height: obj.height || height,
  8691. bottom: obj.bottom || top + (obj.height || height),
  8692. width: obj.width || width
  8693. };
  8694. return ret;
  8695. }; // Move a StyleBox to its specified, or next best, position. The containerBox
  8696. // is the box that contains the StyleBox, such as a div. boxPositions are
  8697. // a list of other boxes that the styleBox can't overlap with.
  8698. function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
  8699. // Find the best position for a cue box, b, on the video. The axis parameter
  8700. // is a list of axis, the order of which, it will move the box along. For example:
  8701. // Passing ["+x", "-x"] will move the box first along the x axis in the positive
  8702. // direction. If it doesn't find a good position for it there it will then move
  8703. // it along the x axis in the negative direction.
  8704. function findBestPosition(b, axis) {
  8705. var bestPosition,
  8706. specifiedPosition = new BoxPosition(b),
  8707. percentage = 1; // Highest possible so the first thing we get is better.
  8708. for (var i = 0; i < axis.length; i++) {
  8709. while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
  8710. b.move(axis[i]);
  8711. } // We found a spot where we aren't overlapping anything. This is our
  8712. // best position.
  8713. if (b.within(containerBox)) {
  8714. return b;
  8715. }
  8716. var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try
  8717. // then remember this position as the best position.
  8718. if (percentage > p) {
  8719. bestPosition = new BoxPosition(b);
  8720. percentage = p;
  8721. } // Reset the box position to the specified position.
  8722. b = new BoxPosition(specifiedPosition);
  8723. }
  8724. return bestPosition || specifiedPosition;
  8725. }
  8726. var boxPosition = new BoxPosition(styleBox),
  8727. cue = styleBox.cue,
  8728. linePos = computeLinePos(cue),
  8729. axis = []; // If we have a line number to align the cue to.
  8730. if (cue.snapToLines) {
  8731. var size;
  8732. switch (cue.vertical) {
  8733. case "":
  8734. axis = ["+y", "-y"];
  8735. size = "height";
  8736. break;
  8737. case "rl":
  8738. axis = ["+x", "-x"];
  8739. size = "width";
  8740. break;
  8741. case "lr":
  8742. axis = ["-x", "+x"];
  8743. size = "width";
  8744. break;
  8745. }
  8746. var step = boxPosition.lineHeight,
  8747. position = step * Math.round(linePos),
  8748. maxPosition = containerBox[size] + step,
  8749. initialAxis = axis[0]; // If the specified intial position is greater then the max position then
  8750. // clamp the box to the amount of steps it would take for the box to
  8751. // reach the max position.
  8752. if (Math.abs(position) > maxPosition) {
  8753. position = position < 0 ? -1 : 1;
  8754. position *= Math.ceil(maxPosition / step) * step;
  8755. } // If computed line position returns negative then line numbers are
  8756. // relative to the bottom of the video instead of the top. Therefore, we
  8757. // need to increase our initial position by the length or width of the
  8758. // video, depending on the writing direction, and reverse our axis directions.
  8759. if (linePos < 0) {
  8760. position += cue.vertical === "" ? containerBox.height : containerBox.width;
  8761. axis = axis.reverse();
  8762. } // Move the box to the specified position. This may not be its best
  8763. // position.
  8764. boxPosition.move(initialAxis, position);
  8765. } else {
  8766. // If we have a percentage line value for the cue.
  8767. var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
  8768. switch (cue.lineAlign) {
  8769. case "middle":
  8770. linePos -= calculatedPercentage / 2;
  8771. break;
  8772. case "end":
  8773. linePos -= calculatedPercentage;
  8774. break;
  8775. } // Apply initial line position to the cue box.
  8776. switch (cue.vertical) {
  8777. case "":
  8778. styleBox.applyStyles({
  8779. top: styleBox.formatStyle(linePos, "%")
  8780. });
  8781. break;
  8782. case "rl":
  8783. styleBox.applyStyles({
  8784. left: styleBox.formatStyle(linePos, "%")
  8785. });
  8786. break;
  8787. case "lr":
  8788. styleBox.applyStyles({
  8789. right: styleBox.formatStyle(linePos, "%")
  8790. });
  8791. break;
  8792. }
  8793. axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning
  8794. // to it.
  8795. boxPosition = new BoxPosition(styleBox);
  8796. }
  8797. var bestPosition = findBestPosition(boxPosition, axis);
  8798. styleBox.move(bestPosition.toCSSCompatValues(containerBox));
  8799. }
  8800. function WebVTT$1() {} // Nothing
  8801. // Helper to allow strings to be decoded instead of the default binary utf8 data.
  8802. WebVTT$1.StringDecoder = function () {
  8803. return {
  8804. decode: function decode(data) {
  8805. if (!data) {
  8806. return "";
  8807. }
  8808. if (typeof data !== "string") {
  8809. throw new Error("Error - expected string data.");
  8810. }
  8811. return decodeURIComponent(encodeURIComponent(data));
  8812. }
  8813. };
  8814. };
  8815. WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
  8816. if (!window || !cuetext) {
  8817. return null;
  8818. }
  8819. return parseContent(window, cuetext);
  8820. };
  8821. var FONT_SIZE_PERCENT = 0.05;
  8822. var FONT_STYLE = "sans-serif";
  8823. var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it.
  8824. // @param overlay A block level element (usually a div) that the computed cues
  8825. // and regions will be placed into.
  8826. WebVTT$1.processCues = function (window, cues, overlay) {
  8827. if (!window || !cues || !overlay) {
  8828. return null;
  8829. } // Remove all previous children.
  8830. while (overlay.firstChild) {
  8831. overlay.removeChild(overlay.firstChild);
  8832. }
  8833. var paddedOverlay = window.document.createElement("div");
  8834. paddedOverlay.style.position = "absolute";
  8835. paddedOverlay.style.left = "0";
  8836. paddedOverlay.style.right = "0";
  8837. paddedOverlay.style.top = "0";
  8838. paddedOverlay.style.bottom = "0";
  8839. paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
  8840. overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could
  8841. // be the case if a cue's state has been changed since the last computation or
  8842. // if it has not been computed yet.
  8843. function shouldCompute(cues) {
  8844. for (var i = 0; i < cues.length; i++) {
  8845. if (cues[i].hasBeenReset || !cues[i].displayState) {
  8846. return true;
  8847. }
  8848. }
  8849. return false;
  8850. } // We don't need to recompute the cues' display states. Just reuse them.
  8851. if (!shouldCompute(cues)) {
  8852. for (var i = 0; i < cues.length; i++) {
  8853. paddedOverlay.appendChild(cues[i].displayState);
  8854. }
  8855. return;
  8856. }
  8857. var boxPositions = [],
  8858. containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
  8859. fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
  8860. var styleOptions = {
  8861. font: fontSize + "px " + FONT_STYLE
  8862. };
  8863. (function () {
  8864. var styleBox, cue;
  8865. for (var i = 0; i < cues.length; i++) {
  8866. cue = cues[i]; // Compute the intial position and styles of the cue div.
  8867. styleBox = new CueStyleBox(window, cue, styleOptions);
  8868. paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position.
  8869. moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later
  8870. // if we don't have too.
  8871. cue.displayState = styleBox.div;
  8872. boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
  8873. }
  8874. })();
  8875. };
  8876. WebVTT$1.Parser = function (window, vttjs, decoder) {
  8877. if (!decoder) {
  8878. decoder = vttjs;
  8879. vttjs = {};
  8880. }
  8881. if (!vttjs) {
  8882. vttjs = {};
  8883. }
  8884. this.window = window;
  8885. this.vttjs = vttjs;
  8886. this.state = "INITIAL";
  8887. this.buffer = "";
  8888. this.decoder = decoder || new TextDecoder("utf8");
  8889. this.regionList = [];
  8890. };
  8891. WebVTT$1.Parser.prototype = {
  8892. // If the error is a ParsingError then report it to the consumer if
  8893. // possible. If it's not a ParsingError then throw it like normal.
  8894. reportOrThrowError: function reportOrThrowError(e) {
  8895. if (e instanceof ParsingError) {
  8896. this.onparsingerror && this.onparsingerror(e);
  8897. } else {
  8898. throw e;
  8899. }
  8900. },
  8901. parse: function parse(data) {
  8902. var self = this; // If there is no data then we won't decode it, but will just try to parse
  8903. // whatever is in buffer already. This may occur in circumstances, for
  8904. // example when flush() is called.
  8905. if (data) {
  8906. // Try to decode the data that we received.
  8907. self.buffer += self.decoder.decode(data, {
  8908. stream: true
  8909. });
  8910. }
  8911. function collectNextLine() {
  8912. var buffer = self.buffer;
  8913. var pos = 0;
  8914. while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
  8915. ++pos;
  8916. }
  8917. var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below.
  8918. if (buffer[pos] === '\r') {
  8919. ++pos;
  8920. }
  8921. if (buffer[pos] === '\n') {
  8922. ++pos;
  8923. }
  8924. self.buffer = buffer.substr(pos);
  8925. return line;
  8926. } // 3.4 WebVTT region and WebVTT region settings syntax
  8927. function parseRegion(input) {
  8928. var settings = new Settings();
  8929. parseOptions(input, function (k, v) {
  8930. switch (k) {
  8931. case "id":
  8932. settings.set(k, v);
  8933. break;
  8934. case "width":
  8935. settings.percent(k, v);
  8936. break;
  8937. case "lines":
  8938. settings.integer(k, v);
  8939. break;
  8940. case "regionanchor":
  8941. case "viewportanchor":
  8942. var xy = v.split(',');
  8943. if (xy.length !== 2) {
  8944. break;
  8945. } // We have to make sure both x and y parse, so use a temporary
  8946. // settings object here.
  8947. var anchor = new Settings();
  8948. anchor.percent("x", xy[0]);
  8949. anchor.percent("y", xy[1]);
  8950. if (!anchor.has("x") || !anchor.has("y")) {
  8951. break;
  8952. }
  8953. settings.set(k + "X", anchor.get("x"));
  8954. settings.set(k + "Y", anchor.get("y"));
  8955. break;
  8956. case "scroll":
  8957. settings.alt(k, v, ["up"]);
  8958. break;
  8959. }
  8960. }, /=/, /\s/); // Create the region, using default values for any values that were not
  8961. // specified.
  8962. if (settings.has("id")) {
  8963. var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
  8964. region.width = settings.get("width", 100);
  8965. region.lines = settings.get("lines", 3);
  8966. region.regionAnchorX = settings.get("regionanchorX", 0);
  8967. region.regionAnchorY = settings.get("regionanchorY", 100);
  8968. region.viewportAnchorX = settings.get("viewportanchorX", 0);
  8969. region.viewportAnchorY = settings.get("viewportanchorY", 100);
  8970. region.scroll = settings.get("scroll", ""); // Register the region.
  8971. self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that
  8972. // reference it.
  8973. self.regionList.push({
  8974. id: settings.get("id"),
  8975. region: region
  8976. });
  8977. }
  8978. } // draft-pantos-http-live-streaming-20
  8979. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
  8980. // 3.5 WebVTT
  8981. function parseTimestampMap(input) {
  8982. var settings = new Settings();
  8983. parseOptions(input, function (k, v) {
  8984. switch (k) {
  8985. case "MPEGT":
  8986. settings.integer(k + 'S', v);
  8987. break;
  8988. case "LOCA":
  8989. settings.set(k + 'L', parseTimeStamp(v));
  8990. break;
  8991. }
  8992. }, /[^\d]:/, /,/);
  8993. self.ontimestampmap && self.ontimestampmap({
  8994. "MPEGTS": settings.get("MPEGTS"),
  8995. "LOCAL": settings.get("LOCAL")
  8996. });
  8997. } // 3.2 WebVTT metadata header syntax
  8998. function parseHeader(input) {
  8999. if (input.match(/X-TIMESTAMP-MAP/)) {
  9000. // This line contains HLS X-TIMESTAMP-MAP metadata
  9001. parseOptions(input, function (k, v) {
  9002. switch (k) {
  9003. case "X-TIMESTAMP-MAP":
  9004. parseTimestampMap(v);
  9005. break;
  9006. }
  9007. }, /=/);
  9008. } else {
  9009. parseOptions(input, function (k, v) {
  9010. switch (k) {
  9011. case "Region":
  9012. // 3.3 WebVTT region metadata header syntax
  9013. parseRegion(v);
  9014. break;
  9015. }
  9016. }, /:/);
  9017. }
  9018. } // 5.1 WebVTT file parsing.
  9019. try {
  9020. var line;
  9021. if (self.state === "INITIAL") {
  9022. // We can't start parsing until we have the first line.
  9023. if (!/\r\n|\n/.test(self.buffer)) {
  9024. return this;
  9025. }
  9026. line = collectNextLine();
  9027. var m = line.match(/^WEBVTT([ \t].*)?$/);
  9028. if (!m || !m[0]) {
  9029. throw new ParsingError(ParsingError.Errors.BadSignature);
  9030. }
  9031. self.state = "HEADER";
  9032. }
  9033. var alreadyCollectedLine = false;
  9034. while (self.buffer) {
  9035. // We can't parse a line until we have the full line.
  9036. if (!/\r\n|\n/.test(self.buffer)) {
  9037. return this;
  9038. }
  9039. if (!alreadyCollectedLine) {
  9040. line = collectNextLine();
  9041. } else {
  9042. alreadyCollectedLine = false;
  9043. }
  9044. switch (self.state) {
  9045. case "HEADER":
  9046. // 13-18 - Allow a header (metadata) under the WEBVTT line.
  9047. if (/:/.test(line)) {
  9048. parseHeader(line);
  9049. } else if (!line) {
  9050. // An empty line terminates the header and starts the body (cues).
  9051. self.state = "ID";
  9052. }
  9053. continue;
  9054. case "NOTE":
  9055. // Ignore NOTE blocks.
  9056. if (!line) {
  9057. self.state = "ID";
  9058. }
  9059. continue;
  9060. case "ID":
  9061. // Check for the start of NOTE blocks.
  9062. if (/^NOTE($|[ \t])/.test(line)) {
  9063. self.state = "NOTE";
  9064. break;
  9065. } // 19-29 - Allow any number of line terminators, then initialize new cue values.
  9066. if (!line) {
  9067. continue;
  9068. }
  9069. self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
  9070. self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.
  9071. if (line.indexOf("-->") === -1) {
  9072. self.cue.id = line;
  9073. continue;
  9074. }
  9075. // Process line as start of a cue.
  9076. /*falls through*/
  9077. case "CUE":
  9078. // 40 - Collect cue timings and settings.
  9079. try {
  9080. parseCue(line, self.cue, self.regionList);
  9081. } catch (e) {
  9082. self.reportOrThrowError(e); // In case of an error ignore rest of the cue.
  9083. self.cue = null;
  9084. self.state = "BADCUE";
  9085. continue;
  9086. }
  9087. self.state = "CUETEXT";
  9088. continue;
  9089. case "CUETEXT":
  9090. var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue.
  9091. // 35 - If we have the special substring '-->' then report the cue,
  9092. // but do not collect the line as we need to process the current
  9093. // one as a new cue.
  9094. if (!line || hasSubstring && (alreadyCollectedLine = true)) {
  9095. // We are done parsing self cue.
  9096. self.oncue && self.oncue(self.cue);
  9097. self.cue = null;
  9098. self.state = "ID";
  9099. continue;
  9100. }
  9101. if (self.cue.text) {
  9102. self.cue.text += "\n";
  9103. }
  9104. self.cue.text += line;
  9105. continue;
  9106. case "BADCUE":
  9107. // BADCUE
  9108. // 54-62 - Collect and discard the remaining cue.
  9109. if (!line) {
  9110. self.state = "ID";
  9111. }
  9112. continue;
  9113. }
  9114. }
  9115. } catch (e) {
  9116. self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have.
  9117. if (self.state === "CUETEXT" && self.cue && self.oncue) {
  9118. self.oncue(self.cue);
  9119. }
  9120. self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise
  9121. // another exception occurred so enter BADCUE state.
  9122. self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
  9123. }
  9124. return this;
  9125. },
  9126. flush: function flush() {
  9127. var self = this;
  9128. try {
  9129. // Finish decoding the stream.
  9130. self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region.
  9131. if (self.cue || self.state === "HEADER") {
  9132. self.buffer += "\n\n";
  9133. self.parse();
  9134. } // If we've flushed, parsed, and we're still on the INITIAL state then
  9135. // that means we don't have enough of the stream to parse the first
  9136. // line.
  9137. if (self.state === "INITIAL") {
  9138. throw new ParsingError(ParsingError.Errors.BadSignature);
  9139. }
  9140. } catch (e) {
  9141. self.reportOrThrowError(e);
  9142. }
  9143. self.onflush && self.onflush();
  9144. return this;
  9145. }
  9146. };
  9147. var vtt = WebVTT$1;
  9148. /**
  9149. * Copyright 2013 vtt.js Contributors
  9150. *
  9151. * Licensed under the Apache License, Version 2.0 (the "License");
  9152. * you may not use this file except in compliance with the License.
  9153. * You may obtain a copy of the License at
  9154. *
  9155. * http://www.apache.org/licenses/LICENSE-2.0
  9156. *
  9157. * Unless required by applicable law or agreed to in writing, software
  9158. * distributed under the License is distributed on an "AS IS" BASIS,
  9159. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9160. * See the License for the specific language governing permissions and
  9161. * limitations under the License.
  9162. */
  9163. var autoKeyword = "auto";
  9164. var directionSetting = {
  9165. "": 1,
  9166. "lr": 1,
  9167. "rl": 1
  9168. };
  9169. var alignSetting = {
  9170. "start": 1,
  9171. "middle": 1,
  9172. "end": 1,
  9173. "left": 1,
  9174. "right": 1
  9175. };
  9176. function findDirectionSetting(value) {
  9177. if (typeof value !== "string") {
  9178. return false;
  9179. }
  9180. var dir = directionSetting[value.toLowerCase()];
  9181. return dir ? value.toLowerCase() : false;
  9182. }
  9183. function findAlignSetting(value) {
  9184. if (typeof value !== "string") {
  9185. return false;
  9186. }
  9187. var align = alignSetting[value.toLowerCase()];
  9188. return align ? value.toLowerCase() : false;
  9189. }
  9190. function VTTCue(startTime, endTime, text) {
  9191. /**
  9192. * Shim implementation specific properties. These properties are not in
  9193. * the spec.
  9194. */
  9195. // Lets us know when the VTTCue's data has changed in such a way that we need
  9196. // to recompute its display state. This lets us compute its display state
  9197. // lazily.
  9198. this.hasBeenReset = false;
  9199. /**
  9200. * VTTCue and TextTrackCue properties
  9201. * http://dev.w3.org/html5/webvtt/#vttcue-interface
  9202. */
  9203. var _id = "";
  9204. var _pauseOnExit = false;
  9205. var _startTime = startTime;
  9206. var _endTime = endTime;
  9207. var _text = text;
  9208. var _region = null;
  9209. var _vertical = "";
  9210. var _snapToLines = true;
  9211. var _line = "auto";
  9212. var _lineAlign = "start";
  9213. var _position = 50;
  9214. var _positionAlign = "middle";
  9215. var _size = 50;
  9216. var _align = "middle";
  9217. Object.defineProperties(this, {
  9218. "id": {
  9219. enumerable: true,
  9220. get: function get() {
  9221. return _id;
  9222. },
  9223. set: function set(value) {
  9224. _id = "" + value;
  9225. }
  9226. },
  9227. "pauseOnExit": {
  9228. enumerable: true,
  9229. get: function get() {
  9230. return _pauseOnExit;
  9231. },
  9232. set: function set(value) {
  9233. _pauseOnExit = !!value;
  9234. }
  9235. },
  9236. "startTime": {
  9237. enumerable: true,
  9238. get: function get() {
  9239. return _startTime;
  9240. },
  9241. set: function set(value) {
  9242. if (typeof value !== "number") {
  9243. throw new TypeError("Start time must be set to a number.");
  9244. }
  9245. _startTime = value;
  9246. this.hasBeenReset = true;
  9247. }
  9248. },
  9249. "endTime": {
  9250. enumerable: true,
  9251. get: function get() {
  9252. return _endTime;
  9253. },
  9254. set: function set(value) {
  9255. if (typeof value !== "number") {
  9256. throw new TypeError("End time must be set to a number.");
  9257. }
  9258. _endTime = value;
  9259. this.hasBeenReset = true;
  9260. }
  9261. },
  9262. "text": {
  9263. enumerable: true,
  9264. get: function get() {
  9265. return _text;
  9266. },
  9267. set: function set(value) {
  9268. _text = "" + value;
  9269. this.hasBeenReset = true;
  9270. }
  9271. },
  9272. "region": {
  9273. enumerable: true,
  9274. get: function get() {
  9275. return _region;
  9276. },
  9277. set: function set(value) {
  9278. _region = value;
  9279. this.hasBeenReset = true;
  9280. }
  9281. },
  9282. "vertical": {
  9283. enumerable: true,
  9284. get: function get() {
  9285. return _vertical;
  9286. },
  9287. set: function set(value) {
  9288. var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string.
  9289. if (setting === false) {
  9290. throw new SyntaxError("An invalid or illegal string was specified.");
  9291. }
  9292. _vertical = setting;
  9293. this.hasBeenReset = true;
  9294. }
  9295. },
  9296. "snapToLines": {
  9297. enumerable: true,
  9298. get: function get() {
  9299. return _snapToLines;
  9300. },
  9301. set: function set(value) {
  9302. _snapToLines = !!value;
  9303. this.hasBeenReset = true;
  9304. }
  9305. },
  9306. "line": {
  9307. enumerable: true,
  9308. get: function get() {
  9309. return _line;
  9310. },
  9311. set: function set(value) {
  9312. if (typeof value !== "number" && value !== autoKeyword) {
  9313. throw new SyntaxError("An invalid number or illegal string was specified.");
  9314. }
  9315. _line = value;
  9316. this.hasBeenReset = true;
  9317. }
  9318. },
  9319. "lineAlign": {
  9320. enumerable: true,
  9321. get: function get() {
  9322. return _lineAlign;
  9323. },
  9324. set: function set(value) {
  9325. var setting = findAlignSetting(value);
  9326. if (!setting) {
  9327. throw new SyntaxError("An invalid or illegal string was specified.");
  9328. }
  9329. _lineAlign = setting;
  9330. this.hasBeenReset = true;
  9331. }
  9332. },
  9333. "position": {
  9334. enumerable: true,
  9335. get: function get() {
  9336. return _position;
  9337. },
  9338. set: function set(value) {
  9339. if (value < 0 || value > 100) {
  9340. throw new Error("Position must be between 0 and 100.");
  9341. }
  9342. _position = value;
  9343. this.hasBeenReset = true;
  9344. }
  9345. },
  9346. "positionAlign": {
  9347. enumerable: true,
  9348. get: function get() {
  9349. return _positionAlign;
  9350. },
  9351. set: function set(value) {
  9352. var setting = findAlignSetting(value);
  9353. if (!setting) {
  9354. throw new SyntaxError("An invalid or illegal string was specified.");
  9355. }
  9356. _positionAlign = setting;
  9357. this.hasBeenReset = true;
  9358. }
  9359. },
  9360. "size": {
  9361. enumerable: true,
  9362. get: function get() {
  9363. return _size;
  9364. },
  9365. set: function set(value) {
  9366. if (value < 0 || value > 100) {
  9367. throw new Error("Size must be between 0 and 100.");
  9368. }
  9369. _size = value;
  9370. this.hasBeenReset = true;
  9371. }
  9372. },
  9373. "align": {
  9374. enumerable: true,
  9375. get: function get() {
  9376. return _align;
  9377. },
  9378. set: function set(value) {
  9379. var setting = findAlignSetting(value);
  9380. if (!setting) {
  9381. throw new SyntaxError("An invalid or illegal string was specified.");
  9382. }
  9383. _align = setting;
  9384. this.hasBeenReset = true;
  9385. }
  9386. }
  9387. });
  9388. /**
  9389. * Other <track> spec defined properties
  9390. */
  9391. // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
  9392. this.displayState = undefined;
  9393. }
  9394. /**
  9395. * VTTCue methods
  9396. */
  9397. VTTCue.prototype.getCueAsHTML = function () {
  9398. // Assume WebVTT.convertCueToDOMTree is on the global.
  9399. return WebVTT.convertCueToDOMTree(window, this.text);
  9400. };
  9401. var vttcue = VTTCue;
  9402. /**
  9403. * Copyright 2013 vtt.js Contributors
  9404. *
  9405. * Licensed under the Apache License, Version 2.0 (the "License");
  9406. * you may not use this file except in compliance with the License.
  9407. * You may obtain a copy of the License at
  9408. *
  9409. * http://www.apache.org/licenses/LICENSE-2.0
  9410. *
  9411. * Unless required by applicable law or agreed to in writing, software
  9412. * distributed under the License is distributed on an "AS IS" BASIS,
  9413. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9414. * See the License for the specific language governing permissions and
  9415. * limitations under the License.
  9416. */
  9417. var scrollSetting = {
  9418. "": true,
  9419. "up": true
  9420. };
  9421. function findScrollSetting(value) {
  9422. if (typeof value !== "string") {
  9423. return false;
  9424. }
  9425. var scroll = scrollSetting[value.toLowerCase()];
  9426. return scroll ? value.toLowerCase() : false;
  9427. }
  9428. function isValidPercentValue(value) {
  9429. return typeof value === "number" && value >= 0 && value <= 100;
  9430. } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
  9431. function VTTRegion() {
  9432. var _width = 100;
  9433. var _lines = 3;
  9434. var _regionAnchorX = 0;
  9435. var _regionAnchorY = 100;
  9436. var _viewportAnchorX = 0;
  9437. var _viewportAnchorY = 100;
  9438. var _scroll = "";
  9439. Object.defineProperties(this, {
  9440. "width": {
  9441. enumerable: true,
  9442. get: function get() {
  9443. return _width;
  9444. },
  9445. set: function set(value) {
  9446. if (!isValidPercentValue(value)) {
  9447. throw new Error("Width must be between 0 and 100.");
  9448. }
  9449. _width = value;
  9450. }
  9451. },
  9452. "lines": {
  9453. enumerable: true,
  9454. get: function get() {
  9455. return _lines;
  9456. },
  9457. set: function set(value) {
  9458. if (typeof value !== "number") {
  9459. throw new TypeError("Lines must be set to a number.");
  9460. }
  9461. _lines = value;
  9462. }
  9463. },
  9464. "regionAnchorY": {
  9465. enumerable: true,
  9466. get: function get() {
  9467. return _regionAnchorY;
  9468. },
  9469. set: function set(value) {
  9470. if (!isValidPercentValue(value)) {
  9471. throw new Error("RegionAnchorX must be between 0 and 100.");
  9472. }
  9473. _regionAnchorY = value;
  9474. }
  9475. },
  9476. "regionAnchorX": {
  9477. enumerable: true,
  9478. get: function get() {
  9479. return _regionAnchorX;
  9480. },
  9481. set: function set(value) {
  9482. if (!isValidPercentValue(value)) {
  9483. throw new Error("RegionAnchorY must be between 0 and 100.");
  9484. }
  9485. _regionAnchorX = value;
  9486. }
  9487. },
  9488. "viewportAnchorY": {
  9489. enumerable: true,
  9490. get: function get() {
  9491. return _viewportAnchorY;
  9492. },
  9493. set: function set(value) {
  9494. if (!isValidPercentValue(value)) {
  9495. throw new Error("ViewportAnchorY must be between 0 and 100.");
  9496. }
  9497. _viewportAnchorY = value;
  9498. }
  9499. },
  9500. "viewportAnchorX": {
  9501. enumerable: true,
  9502. get: function get() {
  9503. return _viewportAnchorX;
  9504. },
  9505. set: function set(value) {
  9506. if (!isValidPercentValue(value)) {
  9507. throw new Error("ViewportAnchorX must be between 0 and 100.");
  9508. }
  9509. _viewportAnchorX = value;
  9510. }
  9511. },
  9512. "scroll": {
  9513. enumerable: true,
  9514. get: function get() {
  9515. return _scroll;
  9516. },
  9517. set: function set(value) {
  9518. var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value.
  9519. if (setting === false) {
  9520. throw new SyntaxError("An invalid or illegal string was specified.");
  9521. }
  9522. _scroll = setting;
  9523. }
  9524. }
  9525. });
  9526. }
  9527. var vttregion = VTTRegion;
  9528. var browserIndex = createCommonjsModule(function (module) {
  9529. /**
  9530. * Copyright 2013 vtt.js Contributors
  9531. *
  9532. * Licensed under the Apache License, Version 2.0 (the "License");
  9533. * you may not use this file except in compliance with the License.
  9534. * You may obtain a copy of the License at
  9535. *
  9536. * http://www.apache.org/licenses/LICENSE-2.0
  9537. *
  9538. * Unless required by applicable law or agreed to in writing, software
  9539. * distributed under the License is distributed on an "AS IS" BASIS,
  9540. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9541. * See the License for the specific language governing permissions and
  9542. * limitations under the License.
  9543. */
  9544. // Default exports for Node. Export the extended versions of VTTCue and
  9545. // VTTRegion in Node since we likely want the capability to convert back and
  9546. // forth between JSON. If we don't then it's not that big of a deal since we're
  9547. // off browser.
  9548. var vttjs = module.exports = {
  9549. WebVTT: vtt,
  9550. VTTCue: vttcue,
  9551. VTTRegion: vttregion
  9552. };
  9553. window$1.vttjs = vttjs;
  9554. window$1.WebVTT = vttjs.WebVTT;
  9555. var cueShim = vttjs.VTTCue;
  9556. var regionShim = vttjs.VTTRegion;
  9557. var nativeVTTCue = window$1.VTTCue;
  9558. var nativeVTTRegion = window$1.VTTRegion;
  9559. vttjs.shim = function () {
  9560. window$1.VTTCue = cueShim;
  9561. window$1.VTTRegion = regionShim;
  9562. };
  9563. vttjs.restore = function () {
  9564. window$1.VTTCue = nativeVTTCue;
  9565. window$1.VTTRegion = nativeVTTRegion;
  9566. };
  9567. if (!window$1.VTTCue) {
  9568. vttjs.shim();
  9569. }
  9570. });
  9571. var browserIndex_1 = browserIndex.WebVTT;
  9572. var browserIndex_2 = browserIndex.VTTCue;
  9573. var browserIndex_3 = browserIndex.VTTRegion;
  9574. /**
  9575. * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
  9576. * that just contains the src url alone.
  9577. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
  9578. * `var SourceString = 'http://example.com/some-video.mp4';`
  9579. *
  9580. * @typedef {Object|string} Tech~SourceObject
  9581. *
  9582. * @property {string} src
  9583. * The url to the source
  9584. *
  9585. * @property {string} type
  9586. * The mime type of the source
  9587. */
  9588. /**
  9589. * A function used by {@link Tech} to create a new {@link TextTrack}.
  9590. *
  9591. * @private
  9592. *
  9593. * @param {Tech} self
  9594. * An instance of the Tech class.
  9595. *
  9596. * @param {string} kind
  9597. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  9598. *
  9599. * @param {string} [label]
  9600. * Label to identify the text track
  9601. *
  9602. * @param {string} [language]
  9603. * Two letter language abbreviation
  9604. *
  9605. * @param {Object} [options={}]
  9606. * An object with additional text track options
  9607. *
  9608. * @return {TextTrack}
  9609. * The text track that was created.
  9610. */
  9611. function createTrackHelper(self, kind, label, language, options) {
  9612. if (options === void 0) {
  9613. options = {};
  9614. }
  9615. var tracks = self.textTracks();
  9616. options.kind = kind;
  9617. if (label) {
  9618. options.label = label;
  9619. }
  9620. if (language) {
  9621. options.language = language;
  9622. }
  9623. options.tech = self;
  9624. var track = new ALL.text.TrackClass(options);
  9625. tracks.addTrack(track);
  9626. return track;
  9627. }
  9628. /**
  9629. * This is the base class for media playback technology controllers, such as
  9630. * {@link Flash} and {@link HTML5}
  9631. *
  9632. * @extends Component
  9633. */
  9634. var Tech =
  9635. /*#__PURE__*/
  9636. function (_Component) {
  9637. _inheritsLoose(Tech, _Component);
  9638. /**
  9639. * Create an instance of this Tech.
  9640. *
  9641. * @param {Object} [options]
  9642. * The key/value store of player options.
  9643. *
  9644. * @param {Component~ReadyCallback} ready
  9645. * Callback function to call when the `HTML5` Tech is ready.
  9646. */
  9647. function Tech(options, ready) {
  9648. var _this;
  9649. if (options === void 0) {
  9650. options = {};
  9651. }
  9652. if (ready === void 0) {
  9653. ready = function ready() {};
  9654. }
  9655. // we don't want the tech to report user activity automatically.
  9656. // This is done manually in addControlsListeners
  9657. options.reportTouchActivity = false;
  9658. _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to
  9659. // implement a very limited played()
  9660. _this.hasStarted_ = false;
  9661. _this.on('playing', function () {
  9662. this.hasStarted_ = true;
  9663. });
  9664. _this.on('loadstart', function () {
  9665. this.hasStarted_ = false;
  9666. });
  9667. ALL.names.forEach(function (name) {
  9668. var props = ALL[name];
  9669. if (options && options[props.getterName]) {
  9670. _this[props.privateName] = options[props.getterName];
  9671. }
  9672. }); // Manually track progress in cases where the browser/flash player doesn't report it.
  9673. if (!_this.featuresProgressEvents) {
  9674. _this.manualProgressOn();
  9675. } // Manually track timeupdates in cases where the browser/flash player doesn't report it.
  9676. if (!_this.featuresTimeupdateEvents) {
  9677. _this.manualTimeUpdatesOn();
  9678. }
  9679. ['Text', 'Audio', 'Video'].forEach(function (track) {
  9680. if (options["native" + track + "Tracks"] === false) {
  9681. _this["featuresNative" + track + "Tracks"] = false;
  9682. }
  9683. });
  9684. if (options.nativeCaptions === false || options.nativeTextTracks === false) {
  9685. _this.featuresNativeTextTracks = false;
  9686. } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
  9687. _this.featuresNativeTextTracks = true;
  9688. }
  9689. if (!_this.featuresNativeTextTracks) {
  9690. _this.emulateTextTracks();
  9691. }
  9692. _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
  9693. _this.initTrackListeners(); // Turn on component tap events only if not using native controls
  9694. if (!options.nativeControlsForTouch) {
  9695. _this.emitTapEvents();
  9696. }
  9697. if (_this.constructor) {
  9698. _this.name_ = _this.constructor.name || 'Unknown Tech';
  9699. }
  9700. return _this;
  9701. }
  9702. /**
  9703. * A special function to trigger source set in a way that will allow player
  9704. * to re-trigger if the player or tech are not ready yet.
  9705. *
  9706. * @fires Tech#sourceset
  9707. * @param {string} src The source string at the time of the source changing.
  9708. */
  9709. var _proto = Tech.prototype;
  9710. _proto.triggerSourceset = function triggerSourceset(src) {
  9711. var _this2 = this;
  9712. if (!this.isReady_) {
  9713. // on initial ready we have to trigger source set
  9714. // 1ms after ready so that player can watch for it.
  9715. this.one('ready', function () {
  9716. return _this2.setTimeout(function () {
  9717. return _this2.triggerSourceset(src);
  9718. }, 1);
  9719. });
  9720. }
  9721. /**
  9722. * Fired when the source is set on the tech causing the media element
  9723. * to reload.
  9724. *
  9725. * @see {@link Player#event:sourceset}
  9726. * @event Tech#sourceset
  9727. * @type {EventTarget~Event}
  9728. */
  9729. this.trigger({
  9730. src: src,
  9731. type: 'sourceset'
  9732. });
  9733. }
  9734. /* Fallbacks for unsupported event types
  9735. ================================================================================ */
  9736. /**
  9737. * Polyfill the `progress` event for browsers that don't support it natively.
  9738. *
  9739. * @see {@link Tech#trackProgress}
  9740. */
  9741. ;
  9742. _proto.manualProgressOn = function manualProgressOn() {
  9743. this.on('durationchange', this.onDurationChange);
  9744. this.manualProgress = true; // Trigger progress watching when a source begins loading
  9745. this.one('ready', this.trackProgress);
  9746. }
  9747. /**
  9748. * Turn off the polyfill for `progress` events that was created in
  9749. * {@link Tech#manualProgressOn}
  9750. */
  9751. ;
  9752. _proto.manualProgressOff = function manualProgressOff() {
  9753. this.manualProgress = false;
  9754. this.stopTrackingProgress();
  9755. this.off('durationchange', this.onDurationChange);
  9756. }
  9757. /**
  9758. * This is used to trigger a `progress` event when the buffered percent changes. It
  9759. * sets an interval function that will be called every 500 milliseconds to check if the
  9760. * buffer end percent has changed.
  9761. *
  9762. * > This function is called by {@link Tech#manualProgressOn}
  9763. *
  9764. * @param {EventTarget~Event} event
  9765. * The `ready` event that caused this to run.
  9766. *
  9767. * @listens Tech#ready
  9768. * @fires Tech#progress
  9769. */
  9770. ;
  9771. _proto.trackProgress = function trackProgress(event) {
  9772. this.stopTrackingProgress();
  9773. this.progressInterval = this.setInterval(bind(this, function () {
  9774. // Don't trigger unless buffered amount is greater than last time
  9775. var numBufferedPercent = this.bufferedPercent();
  9776. if (this.bufferedPercent_ !== numBufferedPercent) {
  9777. /**
  9778. * See {@link Player#progress}
  9779. *
  9780. * @event Tech#progress
  9781. * @type {EventTarget~Event}
  9782. */
  9783. this.trigger('progress');
  9784. }
  9785. this.bufferedPercent_ = numBufferedPercent;
  9786. if (numBufferedPercent === 1) {
  9787. this.stopTrackingProgress();
  9788. }
  9789. }), 500);
  9790. }
  9791. /**
  9792. * Update our internal duration on a `durationchange` event by calling
  9793. * {@link Tech#duration}.
  9794. *
  9795. * @param {EventTarget~Event} event
  9796. * The `durationchange` event that caused this to run.
  9797. *
  9798. * @listens Tech#durationchange
  9799. */
  9800. ;
  9801. _proto.onDurationChange = function onDurationChange(event) {
  9802. this.duration_ = this.duration();
  9803. }
  9804. /**
  9805. * Get and create a `TimeRange` object for buffering.
  9806. *
  9807. * @return {TimeRange}
  9808. * The time range object that was created.
  9809. */
  9810. ;
  9811. _proto.buffered = function buffered() {
  9812. return createTimeRanges(0, 0);
  9813. }
  9814. /**
  9815. * Get the percentage of the current video that is currently buffered.
  9816. *
  9817. * @return {number}
  9818. * A number from 0 to 1 that represents the decimal percentage of the
  9819. * video that is buffered.
  9820. *
  9821. */
  9822. ;
  9823. _proto.bufferedPercent = function bufferedPercent$1() {
  9824. return bufferedPercent(this.buffered(), this.duration_);
  9825. }
  9826. /**
  9827. * Turn off the polyfill for `progress` events that was created in
  9828. * {@link Tech#manualProgressOn}
  9829. * Stop manually tracking progress events by clearing the interval that was set in
  9830. * {@link Tech#trackProgress}.
  9831. */
  9832. ;
  9833. _proto.stopTrackingProgress = function stopTrackingProgress() {
  9834. this.clearInterval(this.progressInterval);
  9835. }
  9836. /**
  9837. * Polyfill the `timeupdate` event for browsers that don't support it.
  9838. *
  9839. * @see {@link Tech#trackCurrentTime}
  9840. */
  9841. ;
  9842. _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
  9843. this.manualTimeUpdates = true;
  9844. this.on('play', this.trackCurrentTime);
  9845. this.on('pause', this.stopTrackingCurrentTime);
  9846. }
  9847. /**
  9848. * Turn off the polyfill for `timeupdate` events that was created in
  9849. * {@link Tech#manualTimeUpdatesOn}
  9850. */
  9851. ;
  9852. _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
  9853. this.manualTimeUpdates = false;
  9854. this.stopTrackingCurrentTime();
  9855. this.off('play', this.trackCurrentTime);
  9856. this.off('pause', this.stopTrackingCurrentTime);
  9857. }
  9858. /**
  9859. * Sets up an interval function to track current time and trigger `timeupdate` every
  9860. * 250 milliseconds.
  9861. *
  9862. * @listens Tech#play
  9863. * @triggers Tech#timeupdate
  9864. */
  9865. ;
  9866. _proto.trackCurrentTime = function trackCurrentTime() {
  9867. if (this.currentTimeInterval) {
  9868. this.stopTrackingCurrentTime();
  9869. }
  9870. this.currentTimeInterval = this.setInterval(function () {
  9871. /**
  9872. * Triggered at an interval of 250ms to indicated that time is passing in the video.
  9873. *
  9874. * @event Tech#timeupdate
  9875. * @type {EventTarget~Event}
  9876. */
  9877. this.trigger({
  9878. type: 'timeupdate',
  9879. target: this,
  9880. manuallyTriggered: true
  9881. }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  9882. }, 250);
  9883. }
  9884. /**
  9885. * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
  9886. * `timeupdate` event is no longer triggered.
  9887. *
  9888. * @listens {Tech#pause}
  9889. */
  9890. ;
  9891. _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
  9892. this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
  9893. // the progress bar won't make it all the way to the end
  9894. this.trigger({
  9895. type: 'timeupdate',
  9896. target: this,
  9897. manuallyTriggered: true
  9898. });
  9899. }
  9900. /**
  9901. * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
  9902. * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
  9903. *
  9904. * @fires Component#dispose
  9905. */
  9906. ;
  9907. _proto.dispose = function dispose() {
  9908. // clear out all tracks because we can't reuse them between techs
  9909. this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
  9910. if (this.manualProgress) {
  9911. this.manualProgressOff();
  9912. }
  9913. if (this.manualTimeUpdates) {
  9914. this.manualTimeUpdatesOff();
  9915. }
  9916. _Component.prototype.dispose.call(this);
  9917. }
  9918. /**
  9919. * Clear out a single `TrackList` or an array of `TrackLists` given their names.
  9920. *
  9921. * > Note: Techs without source handlers should call this between sources for `video`
  9922. * & `audio` tracks. You don't want to use them between tracks!
  9923. *
  9924. * @param {string[]|string} types
  9925. * TrackList names to clear, valid names are `video`, `audio`, and
  9926. * `text`.
  9927. */
  9928. ;
  9929. _proto.clearTracks = function clearTracks(types) {
  9930. var _this3 = this;
  9931. types = [].concat(types); // clear out all tracks because we can't reuse them between techs
  9932. types.forEach(function (type) {
  9933. var list = _this3[type + "Tracks"]() || [];
  9934. var i = list.length;
  9935. while (i--) {
  9936. var track = list[i];
  9937. if (type === 'text') {
  9938. _this3.removeRemoteTextTrack(track);
  9939. }
  9940. list.removeTrack(track);
  9941. }
  9942. });
  9943. }
  9944. /**
  9945. * Remove any TextTracks added via addRemoteTextTrack that are
  9946. * flagged for automatic garbage collection
  9947. */
  9948. ;
  9949. _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
  9950. var list = this.autoRemoteTextTracks_ || [];
  9951. var i = list.length;
  9952. while (i--) {
  9953. var track = list[i];
  9954. this.removeRemoteTextTrack(track);
  9955. }
  9956. }
  9957. /**
  9958. * Reset the tech, which will removes all sources and reset the internal readyState.
  9959. *
  9960. * @abstract
  9961. */
  9962. ;
  9963. _proto.reset = function reset() {}
  9964. /**
  9965. * Get or set an error on the Tech.
  9966. *
  9967. * @param {MediaError} [err]
  9968. * Error to set on the Tech
  9969. *
  9970. * @return {MediaError|null}
  9971. * The current error object on the tech, or null if there isn't one.
  9972. */
  9973. ;
  9974. _proto.error = function error(err) {
  9975. if (err !== undefined) {
  9976. this.error_ = new MediaError(err);
  9977. this.trigger('error');
  9978. }
  9979. return this.error_;
  9980. }
  9981. /**
  9982. * Returns the `TimeRange`s that have been played through for the current source.
  9983. *
  9984. * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
  9985. * It only checks whether the source has played at all or not.
  9986. *
  9987. * @return {TimeRange}
  9988. * - A single time range if this video has played
  9989. * - An empty set of ranges if not.
  9990. */
  9991. ;
  9992. _proto.played = function played() {
  9993. if (this.hasStarted_) {
  9994. return createTimeRanges(0, 0);
  9995. }
  9996. return createTimeRanges();
  9997. }
  9998. /**
  9999. * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
  10000. * previously called.
  10001. *
  10002. * @fires Tech#timeupdate
  10003. */
  10004. ;
  10005. _proto.setCurrentTime = function setCurrentTime() {
  10006. // improve the accuracy of manual timeupdates
  10007. if (this.manualTimeUpdates) {
  10008. /**
  10009. * A manual `timeupdate` event.
  10010. *
  10011. * @event Tech#timeupdate
  10012. * @type {EventTarget~Event}
  10013. */
  10014. this.trigger({
  10015. type: 'timeupdate',
  10016. target: this,
  10017. manuallyTriggered: true
  10018. });
  10019. }
  10020. }
  10021. /**
  10022. * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
  10023. * {@link TextTrackList} events.
  10024. *
  10025. * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
  10026. *
  10027. * @fires Tech#audiotrackchange
  10028. * @fires Tech#videotrackchange
  10029. * @fires Tech#texttrackchange
  10030. */
  10031. ;
  10032. _proto.initTrackListeners = function initTrackListeners() {
  10033. var _this4 = this;
  10034. /**
  10035. * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
  10036. *
  10037. * @event Tech#audiotrackchange
  10038. * @type {EventTarget~Event}
  10039. */
  10040. /**
  10041. * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
  10042. *
  10043. * @event Tech#videotrackchange
  10044. * @type {EventTarget~Event}
  10045. */
  10046. /**
  10047. * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
  10048. *
  10049. * @event Tech#texttrackchange
  10050. * @type {EventTarget~Event}
  10051. */
  10052. NORMAL.names.forEach(function (name) {
  10053. var props = NORMAL[name];
  10054. var trackListChanges = function trackListChanges() {
  10055. _this4.trigger(name + "trackchange");
  10056. };
  10057. var tracks = _this4[props.getterName]();
  10058. tracks.addEventListener('removetrack', trackListChanges);
  10059. tracks.addEventListener('addtrack', trackListChanges);
  10060. _this4.on('dispose', function () {
  10061. tracks.removeEventListener('removetrack', trackListChanges);
  10062. tracks.removeEventListener('addtrack', trackListChanges);
  10063. });
  10064. });
  10065. }
  10066. /**
  10067. * Emulate TextTracks using vtt.js if necessary
  10068. *
  10069. * @fires Tech#vttjsloaded
  10070. * @fires Tech#vttjserror
  10071. */
  10072. ;
  10073. _proto.addWebVttScript_ = function addWebVttScript_() {
  10074. var _this5 = this;
  10075. if (window$1.WebVTT) {
  10076. return;
  10077. } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
  10078. // signals that the Tech is ready at which point Tech.el_ is part of the DOM
  10079. // before inserting the WebVTT script
  10080. if (document.body.contains(this.el())) {
  10081. // load via require if available and vtt.js script location was not passed in
  10082. // as an option. novtt builds will turn the above require call into an empty object
  10083. // which will cause this if check to always fail.
  10084. if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
  10085. this.trigger('vttjsloaded');
  10086. return;
  10087. } // load vtt.js via the script location option or the cdn of no location was
  10088. // passed in
  10089. var script = document.createElement('script');
  10090. script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
  10091. script.onload = function () {
  10092. /**
  10093. * Fired when vtt.js is loaded.
  10094. *
  10095. * @event Tech#vttjsloaded
  10096. * @type {EventTarget~Event}
  10097. */
  10098. _this5.trigger('vttjsloaded');
  10099. };
  10100. script.onerror = function () {
  10101. /**
  10102. * Fired when vtt.js was not loaded due to an error
  10103. *
  10104. * @event Tech#vttjsloaded
  10105. * @type {EventTarget~Event}
  10106. */
  10107. _this5.trigger('vttjserror');
  10108. };
  10109. this.on('dispose', function () {
  10110. script.onload = null;
  10111. script.onerror = null;
  10112. }); // but have not loaded yet and we set it to true before the inject so that
  10113. // we don't overwrite the injected window.WebVTT if it loads right away
  10114. window$1.WebVTT = true;
  10115. this.el().parentNode.appendChild(script);
  10116. } else {
  10117. this.ready(this.addWebVttScript_);
  10118. }
  10119. }
  10120. /**
  10121. * Emulate texttracks
  10122. *
  10123. */
  10124. ;
  10125. _proto.emulateTextTracks = function emulateTextTracks() {
  10126. var _this6 = this;
  10127. var tracks = this.textTracks();
  10128. var remoteTracks = this.remoteTextTracks();
  10129. var handleAddTrack = function handleAddTrack(e) {
  10130. return tracks.addTrack(e.track);
  10131. };
  10132. var handleRemoveTrack = function handleRemoveTrack(e) {
  10133. return tracks.removeTrack(e.track);
  10134. };
  10135. remoteTracks.on('addtrack', handleAddTrack);
  10136. remoteTracks.on('removetrack', handleRemoveTrack);
  10137. this.addWebVttScript_();
  10138. var updateDisplay = function updateDisplay() {
  10139. return _this6.trigger('texttrackchange');
  10140. };
  10141. var textTracksChanges = function textTracksChanges() {
  10142. updateDisplay();
  10143. for (var i = 0; i < tracks.length; i++) {
  10144. var track = tracks[i];
  10145. track.removeEventListener('cuechange', updateDisplay);
  10146. if (track.mode === 'showing') {
  10147. track.addEventListener('cuechange', updateDisplay);
  10148. }
  10149. }
  10150. };
  10151. textTracksChanges();
  10152. tracks.addEventListener('change', textTracksChanges);
  10153. tracks.addEventListener('addtrack', textTracksChanges);
  10154. tracks.addEventListener('removetrack', textTracksChanges);
  10155. this.on('dispose', function () {
  10156. remoteTracks.off('addtrack', handleAddTrack);
  10157. remoteTracks.off('removetrack', handleRemoveTrack);
  10158. tracks.removeEventListener('change', textTracksChanges);
  10159. tracks.removeEventListener('addtrack', textTracksChanges);
  10160. tracks.removeEventListener('removetrack', textTracksChanges);
  10161. for (var i = 0; i < tracks.length; i++) {
  10162. var track = tracks[i];
  10163. track.removeEventListener('cuechange', updateDisplay);
  10164. }
  10165. });
  10166. }
  10167. /**
  10168. * Create and returns a remote {@link TextTrack} object.
  10169. *
  10170. * @param {string} kind
  10171. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  10172. *
  10173. * @param {string} [label]
  10174. * Label to identify the text track
  10175. *
  10176. * @param {string} [language]
  10177. * Two letter language abbreviation
  10178. *
  10179. * @return {TextTrack}
  10180. * The TextTrack that gets created.
  10181. */
  10182. ;
  10183. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  10184. if (!kind) {
  10185. throw new Error('TextTrack kind is required but was not provided');
  10186. }
  10187. return createTrackHelper(this, kind, label, language);
  10188. }
  10189. /**
  10190. * Create an emulated TextTrack for use by addRemoteTextTrack
  10191. *
  10192. * This is intended to be overridden by classes that inherit from
  10193. * Tech in order to create native or custom TextTracks.
  10194. *
  10195. * @param {Object} options
  10196. * The object should contain the options to initialize the TextTrack with.
  10197. *
  10198. * @param {string} [options.kind]
  10199. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  10200. *
  10201. * @param {string} [options.label].
  10202. * Label to identify the text track
  10203. *
  10204. * @param {string} [options.language]
  10205. * Two letter language abbreviation.
  10206. *
  10207. * @return {HTMLTrackElement}
  10208. * The track element that gets created.
  10209. */
  10210. ;
  10211. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  10212. var track = mergeOptions(options, {
  10213. tech: this
  10214. });
  10215. return new REMOTE.remoteTextEl.TrackClass(track);
  10216. }
  10217. /**
  10218. * Creates a remote text track object and returns an html track element.
  10219. *
  10220. * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
  10221. *
  10222. * @param {Object} options
  10223. * See {@link Tech#createRemoteTextTrack} for more detailed properties.
  10224. *
  10225. * @param {boolean} [manualCleanup=true]
  10226. * - When false: the TextTrack will be automatically removed from the video
  10227. * element whenever the source changes
  10228. * - When True: The TextTrack will have to be cleaned up manually
  10229. *
  10230. * @return {HTMLTrackElement}
  10231. * An Html Track Element.
  10232. *
  10233. * @deprecated The default functionality for this function will be equivalent
  10234. * to "manualCleanup=false" in the future. The manualCleanup parameter will
  10235. * also be removed.
  10236. */
  10237. ;
  10238. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  10239. var _this7 = this;
  10240. if (options === void 0) {
  10241. options = {};
  10242. }
  10243. var htmlTrackElement = this.createRemoteTextTrack(options);
  10244. if (manualCleanup !== true && manualCleanup !== false) {
  10245. // deprecation warning
  10246. log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
  10247. manualCleanup = true;
  10248. } // store HTMLTrackElement and TextTrack to remote list
  10249. this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
  10250. this.remoteTextTracks().addTrack(htmlTrackElement.track);
  10251. if (manualCleanup !== true) {
  10252. // create the TextTrackList if it doesn't exist
  10253. this.ready(function () {
  10254. return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
  10255. });
  10256. }
  10257. return htmlTrackElement;
  10258. }
  10259. /**
  10260. * Remove a remote text track from the remote `TextTrackList`.
  10261. *
  10262. * @param {TextTrack} track
  10263. * `TextTrack` to remove from the `TextTrackList`
  10264. */
  10265. ;
  10266. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  10267. var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
  10268. this.remoteTextTrackEls().removeTrackElement_(trackElement);
  10269. this.remoteTextTracks().removeTrack(track);
  10270. this.autoRemoteTextTracks_.removeTrack(track);
  10271. }
  10272. /**
  10273. * Gets available media playback quality metrics as specified by the W3C's Media
  10274. * Playback Quality API.
  10275. *
  10276. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  10277. *
  10278. * @return {Object}
  10279. * An object with supported media playback quality metrics
  10280. *
  10281. * @abstract
  10282. */
  10283. ;
  10284. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  10285. return {};
  10286. }
  10287. /**
  10288. * Attempt to create a floating video window always on top of other windows
  10289. * so that users may continue consuming media while they interact with other
  10290. * content sites, or applications on their device.
  10291. *
  10292. * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
  10293. *
  10294. * @return {Promise|undefined}
  10295. * A promise with a Picture-in-Picture window if the browser supports
  10296. * Promises (or one was passed in as an option). It returns undefined
  10297. * otherwise.
  10298. *
  10299. * @abstract
  10300. */
  10301. ;
  10302. _proto.requestPictureInPicture = function requestPictureInPicture() {
  10303. var PromiseClass = this.options_.Promise || window$1.Promise;
  10304. if (PromiseClass) {
  10305. return PromiseClass.reject();
  10306. }
  10307. }
  10308. /**
  10309. * A method to set a poster from a `Tech`.
  10310. *
  10311. * @abstract
  10312. */
  10313. ;
  10314. _proto.setPoster = function setPoster() {}
  10315. /**
  10316. * A method to check for the presence of the 'playsinline' <video> attribute.
  10317. *
  10318. * @abstract
  10319. */
  10320. ;
  10321. _proto.playsinline = function playsinline() {}
  10322. /**
  10323. * A method to set or unset the 'playsinline' <video> attribute.
  10324. *
  10325. * @abstract
  10326. */
  10327. ;
  10328. _proto.setPlaysinline = function setPlaysinline() {}
  10329. /**
  10330. * Attempt to force override of native audio tracks.
  10331. *
  10332. * @param {boolean} override - If set to true native audio will be overridden,
  10333. * otherwise native audio will potentially be used.
  10334. *
  10335. * @abstract
  10336. */
  10337. ;
  10338. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
  10339. /**
  10340. * Attempt to force override of native video tracks.
  10341. *
  10342. * @param {boolean} override - If set to true native video will be overridden,
  10343. * otherwise native video will potentially be used.
  10344. *
  10345. * @abstract
  10346. */
  10347. ;
  10348. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
  10349. /*
  10350. * Check if the tech can support the given mime-type.
  10351. *
  10352. * The base tech does not support any type, but source handlers might
  10353. * overwrite this.
  10354. *
  10355. * @param {string} type
  10356. * The mimetype to check for support
  10357. *
  10358. * @return {string}
  10359. * 'probably', 'maybe', or empty string
  10360. *
  10361. * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
  10362. *
  10363. * @abstract
  10364. */
  10365. ;
  10366. _proto.canPlayType = function canPlayType() {
  10367. return '';
  10368. }
  10369. /**
  10370. * Check if the type is supported by this tech.
  10371. *
  10372. * The base tech does not support any type, but source handlers might
  10373. * overwrite this.
  10374. *
  10375. * @param {string} type
  10376. * The media type to check
  10377. * @return {string} Returns the native video element's response
  10378. */
  10379. ;
  10380. Tech.canPlayType = function canPlayType() {
  10381. return '';
  10382. }
  10383. /**
  10384. * Check if the tech can support the given source
  10385. *
  10386. * @param {Object} srcObj
  10387. * The source object
  10388. * @param {Object} options
  10389. * The options passed to the tech
  10390. * @return {string} 'probably', 'maybe', or '' (empty string)
  10391. */
  10392. ;
  10393. Tech.canPlaySource = function canPlaySource(srcObj, options) {
  10394. return Tech.canPlayType(srcObj.type);
  10395. }
  10396. /*
  10397. * Return whether the argument is a Tech or not.
  10398. * Can be passed either a Class like `Html5` or a instance like `player.tech_`
  10399. *
  10400. * @param {Object} component
  10401. * The item to check
  10402. *
  10403. * @return {boolean}
  10404. * Whether it is a tech or not
  10405. * - True if it is a tech
  10406. * - False if it is not
  10407. */
  10408. ;
  10409. Tech.isTech = function isTech(component) {
  10410. return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  10411. }
  10412. /**
  10413. * Registers a `Tech` into a shared list for videojs.
  10414. *
  10415. * @param {string} name
  10416. * Name of the `Tech` to register.
  10417. *
  10418. * @param {Object} tech
  10419. * The `Tech` class to register.
  10420. */
  10421. ;
  10422. Tech.registerTech = function registerTech(name, tech) {
  10423. if (!Tech.techs_) {
  10424. Tech.techs_ = {};
  10425. }
  10426. if (!Tech.isTech(tech)) {
  10427. throw new Error("Tech " + name + " must be a Tech");
  10428. }
  10429. if (!Tech.canPlayType) {
  10430. throw new Error('Techs must have a static canPlayType method on them');
  10431. }
  10432. if (!Tech.canPlaySource) {
  10433. throw new Error('Techs must have a static canPlaySource method on them');
  10434. }
  10435. name = toTitleCase(name);
  10436. Tech.techs_[name] = tech;
  10437. if (name !== 'Tech') {
  10438. // camel case the techName for use in techOrder
  10439. Tech.defaultTechOrder_.push(name);
  10440. }
  10441. return tech;
  10442. }
  10443. /**
  10444. * Get a `Tech` from the shared list by name.
  10445. *
  10446. * @param {string} name
  10447. * `camelCase` or `TitleCase` name of the Tech to get
  10448. *
  10449. * @return {Tech|undefined}
  10450. * The `Tech` or undefined if there was no tech with the name requested.
  10451. */
  10452. ;
  10453. Tech.getTech = function getTech(name) {
  10454. if (!name) {
  10455. return;
  10456. }
  10457. name = toTitleCase(name);
  10458. if (Tech.techs_ && Tech.techs_[name]) {
  10459. return Tech.techs_[name];
  10460. }
  10461. if (window$1 && window$1.videojs && window$1.videojs[name]) {
  10462. log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
  10463. return window$1.videojs[name];
  10464. }
  10465. };
  10466. return Tech;
  10467. }(Component);
  10468. /**
  10469. * Get the {@link VideoTrackList}
  10470. *
  10471. * @returns {VideoTrackList}
  10472. * @method Tech.prototype.videoTracks
  10473. */
  10474. /**
  10475. * Get the {@link AudioTrackList}
  10476. *
  10477. * @returns {AudioTrackList}
  10478. * @method Tech.prototype.audioTracks
  10479. */
  10480. /**
  10481. * Get the {@link TextTrackList}
  10482. *
  10483. * @returns {TextTrackList}
  10484. * @method Tech.prototype.textTracks
  10485. */
  10486. /**
  10487. * Get the remote element {@link TextTrackList}
  10488. *
  10489. * @returns {TextTrackList}
  10490. * @method Tech.prototype.remoteTextTracks
  10491. */
  10492. /**
  10493. * Get the remote element {@link HtmlTrackElementList}
  10494. *
  10495. * @returns {HtmlTrackElementList}
  10496. * @method Tech.prototype.remoteTextTrackEls
  10497. */
  10498. ALL.names.forEach(function (name) {
  10499. var props = ALL[name];
  10500. Tech.prototype[props.getterName] = function () {
  10501. this[props.privateName] = this[props.privateName] || new props.ListClass();
  10502. return this[props.privateName];
  10503. };
  10504. });
  10505. /**
  10506. * List of associated text tracks
  10507. *
  10508. * @type {TextTrackList}
  10509. * @private
  10510. * @property Tech#textTracks_
  10511. */
  10512. /**
  10513. * List of associated audio tracks.
  10514. *
  10515. * @type {AudioTrackList}
  10516. * @private
  10517. * @property Tech#audioTracks_
  10518. */
  10519. /**
  10520. * List of associated video tracks.
  10521. *
  10522. * @type {VideoTrackList}
  10523. * @private
  10524. * @property Tech#videoTracks_
  10525. */
  10526. /**
  10527. * Boolean indicating whether the `Tech` supports volume control.
  10528. *
  10529. * @type {boolean}
  10530. * @default
  10531. */
  10532. Tech.prototype.featuresVolumeControl = true;
  10533. /**
  10534. * Boolean indicating whether the `Tech` supports muting volume.
  10535. *
  10536. * @type {bolean}
  10537. * @default
  10538. */
  10539. Tech.prototype.featuresMuteControl = true;
  10540. /**
  10541. * Boolean indicating whether the `Tech` supports fullscreen resize control.
  10542. * Resizing plugins using request fullscreen reloads the plugin
  10543. *
  10544. * @type {boolean}
  10545. * @default
  10546. */
  10547. Tech.prototype.featuresFullscreenResize = false;
  10548. /**
  10549. * Boolean indicating whether the `Tech` supports changing the speed at which the video
  10550. * plays. Examples:
  10551. * - Set player to play 2x (twice) as fast
  10552. * - Set player to play 0.5x (half) as fast
  10553. *
  10554. * @type {boolean}
  10555. * @default
  10556. */
  10557. Tech.prototype.featuresPlaybackRate = false;
  10558. /**
  10559. * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
  10560. * not triggered by video-js-swf. This will be used to determine if
  10561. * {@link Tech#manualProgressOn} should be called.
  10562. *
  10563. * @type {boolean}
  10564. * @default
  10565. */
  10566. Tech.prototype.featuresProgressEvents = false;
  10567. /**
  10568. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  10569. *
  10570. * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
  10571. * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
  10572. * a new source.
  10573. *
  10574. * @type {boolean}
  10575. * @default
  10576. */
  10577. Tech.prototype.featuresSourceset = false;
  10578. /**
  10579. * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
  10580. * not triggered by video-js-swf. This will be used to determine if
  10581. * {@link Tech#manualTimeUpdates} should be called.
  10582. *
  10583. * @type {boolean}
  10584. * @default
  10585. */
  10586. Tech.prototype.featuresTimeupdateEvents = false;
  10587. /**
  10588. * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
  10589. * This will help us integrate with native `TextTrack`s if the browser supports them.
  10590. *
  10591. * @type {boolean}
  10592. * @default
  10593. */
  10594. Tech.prototype.featuresNativeTextTracks = false;
  10595. /**
  10596. * A functional mixin for techs that want to use the Source Handler pattern.
  10597. * Source handlers are scripts for handling specific formats.
  10598. * The source handler pattern is used for adaptive formats (HLS, DASH) that
  10599. * manually load video data and feed it into a Source Buffer (Media Source Extensions)
  10600. * Example: `Tech.withSourceHandlers.call(MyTech);`
  10601. *
  10602. * @param {Tech} _Tech
  10603. * The tech to add source handler functions to.
  10604. *
  10605. * @mixes Tech~SourceHandlerAdditions
  10606. */
  10607. Tech.withSourceHandlers = function (_Tech) {
  10608. /**
  10609. * Register a source handler
  10610. *
  10611. * @param {Function} handler
  10612. * The source handler class
  10613. *
  10614. * @param {number} [index]
  10615. * Register it at the following index
  10616. */
  10617. _Tech.registerSourceHandler = function (handler, index) {
  10618. var handlers = _Tech.sourceHandlers;
  10619. if (!handlers) {
  10620. handlers = _Tech.sourceHandlers = [];
  10621. }
  10622. if (index === undefined) {
  10623. // add to the end of the list
  10624. index = handlers.length;
  10625. }
  10626. handlers.splice(index, 0, handler);
  10627. };
  10628. /**
  10629. * Check if the tech can support the given type. Also checks the
  10630. * Techs sourceHandlers.
  10631. *
  10632. * @param {string} type
  10633. * The mimetype to check.
  10634. *
  10635. * @return {string}
  10636. * 'probably', 'maybe', or '' (empty string)
  10637. */
  10638. _Tech.canPlayType = function (type) {
  10639. var handlers = _Tech.sourceHandlers || [];
  10640. var can;
  10641. for (var i = 0; i < handlers.length; i++) {
  10642. can = handlers[i].canPlayType(type);
  10643. if (can) {
  10644. return can;
  10645. }
  10646. }
  10647. return '';
  10648. };
  10649. /**
  10650. * Returns the first source handler that supports the source.
  10651. *
  10652. * TODO: Answer question: should 'probably' be prioritized over 'maybe'
  10653. *
  10654. * @param {Tech~SourceObject} source
  10655. * The source object
  10656. *
  10657. * @param {Object} options
  10658. * The options passed to the tech
  10659. *
  10660. * @return {SourceHandler|null}
  10661. * The first source handler that supports the source or null if
  10662. * no SourceHandler supports the source
  10663. */
  10664. _Tech.selectSourceHandler = function (source, options) {
  10665. var handlers = _Tech.sourceHandlers || [];
  10666. var can;
  10667. for (var i = 0; i < handlers.length; i++) {
  10668. can = handlers[i].canHandleSource(source, options);
  10669. if (can) {
  10670. return handlers[i];
  10671. }
  10672. }
  10673. return null;
  10674. };
  10675. /**
  10676. * Check if the tech can support the given source.
  10677. *
  10678. * @param {Tech~SourceObject} srcObj
  10679. * The source object
  10680. *
  10681. * @param {Object} options
  10682. * The options passed to the tech
  10683. *
  10684. * @return {string}
  10685. * 'probably', 'maybe', or '' (empty string)
  10686. */
  10687. _Tech.canPlaySource = function (srcObj, options) {
  10688. var sh = _Tech.selectSourceHandler(srcObj, options);
  10689. if (sh) {
  10690. return sh.canHandleSource(srcObj, options);
  10691. }
  10692. return '';
  10693. };
  10694. /**
  10695. * When using a source handler, prefer its implementation of
  10696. * any function normally provided by the tech.
  10697. */
  10698. var deferrable = ['seekable', 'seeking', 'duration'];
  10699. /**
  10700. * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
  10701. * function if it exists, with a fallback to the Techs seekable function.
  10702. *
  10703. * @method _Tech.seekable
  10704. */
  10705. /**
  10706. * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
  10707. * function if it exists, otherwise it will fallback to the techs duration function.
  10708. *
  10709. * @method _Tech.duration
  10710. */
  10711. deferrable.forEach(function (fnName) {
  10712. var originalFn = this[fnName];
  10713. if (typeof originalFn !== 'function') {
  10714. return;
  10715. }
  10716. this[fnName] = function () {
  10717. if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
  10718. return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
  10719. }
  10720. return originalFn.apply(this, arguments);
  10721. };
  10722. }, _Tech.prototype);
  10723. /**
  10724. * Create a function for setting the source using a source object
  10725. * and source handlers.
  10726. * Should never be called unless a source handler was found.
  10727. *
  10728. * @param {Tech~SourceObject} source
  10729. * A source object with src and type keys
  10730. */
  10731. _Tech.prototype.setSource = function (source) {
  10732. var sh = _Tech.selectSourceHandler(source, this.options_);
  10733. if (!sh) {
  10734. // Fall back to a native source hander when unsupported sources are
  10735. // deliberately set
  10736. if (_Tech.nativeSourceHandler) {
  10737. sh = _Tech.nativeSourceHandler;
  10738. } else {
  10739. log.error('No source handler found for the current source.');
  10740. }
  10741. } // Dispose any existing source handler
  10742. this.disposeSourceHandler();
  10743. this.off('dispose', this.disposeSourceHandler);
  10744. if (sh !== _Tech.nativeSourceHandler) {
  10745. this.currentSource_ = source;
  10746. }
  10747. this.sourceHandler_ = sh.handleSource(source, this, this.options_);
  10748. this.one('dispose', this.disposeSourceHandler);
  10749. };
  10750. /**
  10751. * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
  10752. *
  10753. * @listens Tech#dispose
  10754. */
  10755. _Tech.prototype.disposeSourceHandler = function () {
  10756. // if we have a source and get another one
  10757. // then we are loading something new
  10758. // than clear all of our current tracks
  10759. if (this.currentSource_) {
  10760. this.clearTracks(['audio', 'video']);
  10761. this.currentSource_ = null;
  10762. } // always clean up auto-text tracks
  10763. this.cleanupAutoTextTracks();
  10764. if (this.sourceHandler_) {
  10765. if (this.sourceHandler_.dispose) {
  10766. this.sourceHandler_.dispose();
  10767. }
  10768. this.sourceHandler_ = null;
  10769. }
  10770. };
  10771. }; // The base Tech class needs to be registered as a Component. It is the only
  10772. // Tech that can be registered as a Component.
  10773. Component.registerComponent('Tech', Tech);
  10774. Tech.registerTech('Tech', Tech);
  10775. /**
  10776. * A list of techs that should be added to techOrder on Players
  10777. *
  10778. * @private
  10779. */
  10780. Tech.defaultTechOrder_ = [];
  10781. /**
  10782. * @file middleware.js
  10783. * @module middleware
  10784. */
  10785. var middlewares = {};
  10786. var middlewareInstances = {};
  10787. var TERMINATOR = {};
  10788. /**
  10789. * A middleware object is a plain JavaScript object that has methods that
  10790. * match the {@link Tech} methods found in the lists of allowed
  10791. * {@link module:middleware.allowedGetters|getters},
  10792. * {@link module:middleware.allowedSetters|setters}, and
  10793. * {@link module:middleware.allowedMediators|mediators}.
  10794. *
  10795. * @typedef {Object} MiddlewareObject
  10796. */
  10797. /**
  10798. * A middleware factory function that should return a
  10799. * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
  10800. *
  10801. * This factory will be called for each player when needed, with the player
  10802. * passed in as an argument.
  10803. *
  10804. * @callback MiddlewareFactory
  10805. * @param {Player} player
  10806. * A Video.js player.
  10807. */
  10808. /**
  10809. * Define a middleware that the player should use by way of a factory function
  10810. * that returns a middleware object.
  10811. *
  10812. * @param {string} type
  10813. * The MIME type to match or `"*"` for all MIME types.
  10814. *
  10815. * @param {MiddlewareFactory} middleware
  10816. * A middleware factory function that will be executed for
  10817. * matching types.
  10818. */
  10819. function use(type, middleware) {
  10820. middlewares[type] = middlewares[type] || [];
  10821. middlewares[type].push(middleware);
  10822. }
  10823. /**
  10824. * Asynchronously sets a source using middleware by recursing through any
  10825. * matching middlewares and calling `setSource` on each, passing along the
  10826. * previous returned value each time.
  10827. *
  10828. * @param {Player} player
  10829. * A {@link Player} instance.
  10830. *
  10831. * @param {Tech~SourceObject} src
  10832. * A source object.
  10833. *
  10834. * @param {Function}
  10835. * The next middleware to run.
  10836. */
  10837. function setSource(player, src, next) {
  10838. player.setTimeout(function () {
  10839. return setSourceHelper(src, middlewares[src.type], next, player);
  10840. }, 1);
  10841. }
  10842. /**
  10843. * When the tech is set, passes the tech to each middleware's `setTech` method.
  10844. *
  10845. * @param {Object[]} middleware
  10846. * An array of middleware instances.
  10847. *
  10848. * @param {Tech} tech
  10849. * A Video.js tech.
  10850. */
  10851. function setTech(middleware, tech) {
  10852. middleware.forEach(function (mw) {
  10853. return mw.setTech && mw.setTech(tech);
  10854. });
  10855. }
  10856. /**
  10857. * Calls a getter on the tech first, through each middleware
  10858. * from right to left to the player.
  10859. *
  10860. * @param {Object[]} middleware
  10861. * An array of middleware instances.
  10862. *
  10863. * @param {Tech} tech
  10864. * The current tech.
  10865. *
  10866. * @param {string} method
  10867. * A method name.
  10868. *
  10869. * @return {Mixed}
  10870. * The final value from the tech after middleware has intercepted it.
  10871. */
  10872. function get(middleware, tech, method) {
  10873. return middleware.reduceRight(middlewareIterator(method), tech[method]());
  10874. }
  10875. /**
  10876. * Takes the argument given to the player and calls the setter method on each
  10877. * middleware from left to right to the tech.
  10878. *
  10879. * @param {Object[]} middleware
  10880. * An array of middleware instances.
  10881. *
  10882. * @param {Tech} tech
  10883. * The current tech.
  10884. *
  10885. * @param {string} method
  10886. * A method name.
  10887. *
  10888. * @param {Mixed} arg
  10889. * The value to set on the tech.
  10890. *
  10891. * @return {Mixed}
  10892. * The return value of the `method` of the `tech`.
  10893. */
  10894. function set(middleware, tech, method, arg) {
  10895. return tech[method](middleware.reduce(middlewareIterator(method), arg));
  10896. }
  10897. /**
  10898. * Takes the argument given to the player and calls the `call` version of the
  10899. * method on each middleware from left to right.
  10900. *
  10901. * Then, call the passed in method on the tech and return the result unchanged
  10902. * back to the player, through middleware, this time from right to left.
  10903. *
  10904. * @param {Object[]} middleware
  10905. * An array of middleware instances.
  10906. *
  10907. * @param {Tech} tech
  10908. * The current tech.
  10909. *
  10910. * @param {string} method
  10911. * A method name.
  10912. *
  10913. * @param {Mixed} arg
  10914. * The value to set on the tech.
  10915. *
  10916. * @return {Mixed}
  10917. * The return value of the `method` of the `tech`, regardless of the
  10918. * return values of middlewares.
  10919. */
  10920. function mediate(middleware, tech, method, arg) {
  10921. if (arg === void 0) {
  10922. arg = null;
  10923. }
  10924. var callMethod = 'call' + toTitleCase(method);
  10925. var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
  10926. var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
  10927. // prevent confusion if a techs method actually returns null.
  10928. var returnValue = terminated ? null : tech[method](middlewareValue);
  10929. executeRight(middleware, method, returnValue, terminated);
  10930. return returnValue;
  10931. }
  10932. /**
  10933. * Enumeration of allowed getters where the keys are method names.
  10934. *
  10935. * @type {Object}
  10936. */
  10937. var allowedGetters = {
  10938. buffered: 1,
  10939. currentTime: 1,
  10940. duration: 1,
  10941. seekable: 1,
  10942. played: 1,
  10943. paused: 1,
  10944. volume: 1
  10945. };
  10946. /**
  10947. * Enumeration of allowed setters where the keys are method names.
  10948. *
  10949. * @type {Object}
  10950. */
  10951. var allowedSetters = {
  10952. setCurrentTime: 1,
  10953. setVolume: 1
  10954. };
  10955. /**
  10956. * Enumeration of allowed mediators where the keys are method names.
  10957. *
  10958. * @type {Object}
  10959. */
  10960. var allowedMediators = {
  10961. play: 1,
  10962. pause: 1
  10963. };
  10964. function middlewareIterator(method) {
  10965. return function (value, mw) {
  10966. // if the previous middleware terminated, pass along the termination
  10967. if (value === TERMINATOR) {
  10968. return TERMINATOR;
  10969. }
  10970. if (mw[method]) {
  10971. return mw[method](value);
  10972. }
  10973. return value;
  10974. };
  10975. }
  10976. function executeRight(mws, method, value, terminated) {
  10977. for (var i = mws.length - 1; i >= 0; i--) {
  10978. var mw = mws[i];
  10979. if (mw[method]) {
  10980. mw[method](terminated, value);
  10981. }
  10982. }
  10983. }
  10984. /**
  10985. * Clear the middleware cache for a player.
  10986. *
  10987. * @param {Player} player
  10988. * A {@link Player} instance.
  10989. */
  10990. function clearCacheForPlayer(player) {
  10991. middlewareInstances[player.id()] = null;
  10992. }
  10993. /**
  10994. * {
  10995. * [playerId]: [[mwFactory, mwInstance], ...]
  10996. * }
  10997. *
  10998. * @private
  10999. */
  11000. function getOrCreateFactory(player, mwFactory) {
  11001. var mws = middlewareInstances[player.id()];
  11002. var mw = null;
  11003. if (mws === undefined || mws === null) {
  11004. mw = mwFactory(player);
  11005. middlewareInstances[player.id()] = [[mwFactory, mw]];
  11006. return mw;
  11007. }
  11008. for (var i = 0; i < mws.length; i++) {
  11009. var _mws$i = mws[i],
  11010. mwf = _mws$i[0],
  11011. mwi = _mws$i[1];
  11012. if (mwf !== mwFactory) {
  11013. continue;
  11014. }
  11015. mw = mwi;
  11016. }
  11017. if (mw === null) {
  11018. mw = mwFactory(player);
  11019. mws.push([mwFactory, mw]);
  11020. }
  11021. return mw;
  11022. }
  11023. function setSourceHelper(src, middleware, next, player, acc, lastRun) {
  11024. if (src === void 0) {
  11025. src = {};
  11026. }
  11027. if (middleware === void 0) {
  11028. middleware = [];
  11029. }
  11030. if (acc === void 0) {
  11031. acc = [];
  11032. }
  11033. if (lastRun === void 0) {
  11034. lastRun = false;
  11035. }
  11036. var _middleware = middleware,
  11037. mwFactory = _middleware[0],
  11038. mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
  11039. if (typeof mwFactory === 'string') {
  11040. setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
  11041. // then call the mw's setSource method
  11042. } else if (mwFactory) {
  11043. var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
  11044. if (!mw.setSource) {
  11045. acc.push(mw);
  11046. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  11047. }
  11048. mw.setSource(assign({}, src), function (err, _src) {
  11049. // something happened, try the next middleware on the current level
  11050. // make sure to use the old src
  11051. if (err) {
  11052. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  11053. } // we've succeeded, now we need to go deeper
  11054. acc.push(mw); // if it's the same type, continue down the current chain
  11055. // otherwise, we want to go down the new chain
  11056. setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
  11057. });
  11058. } else if (mwrest.length) {
  11059. setSourceHelper(src, mwrest, next, player, acc, lastRun);
  11060. } else if (lastRun) {
  11061. next(src, acc);
  11062. } else {
  11063. setSourceHelper(src, middlewares['*'], next, player, acc, true);
  11064. }
  11065. }
  11066. /**
  11067. * Mimetypes
  11068. *
  11069. * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
  11070. * @typedef Mimetypes~Kind
  11071. * @enum
  11072. */
  11073. var MimetypesKind = {
  11074. opus: 'video/ogg',
  11075. ogv: 'video/ogg',
  11076. mp4: 'video/mp4',
  11077. mov: 'video/mp4',
  11078. m4v: 'video/mp4',
  11079. mkv: 'video/x-matroska',
  11080. m4a: 'audio/mp4',
  11081. mp3: 'audio/mpeg',
  11082. aac: 'audio/aac',
  11083. oga: 'audio/ogg',
  11084. m3u8: 'application/x-mpegURL',
  11085. jpg: 'image/jpeg',
  11086. jpeg: 'image/jpeg',
  11087. gif: 'image/gif',
  11088. png: 'image/png',
  11089. svg: 'image/svg+xml',
  11090. webp: 'image/webp'
  11091. };
  11092. /**
  11093. * Get the mimetype of a given src url if possible
  11094. *
  11095. * @param {string} src
  11096. * The url to the src
  11097. *
  11098. * @return {string}
  11099. * return the mimetype if it was known or empty string otherwise
  11100. */
  11101. var getMimetype = function getMimetype(src) {
  11102. if (src === void 0) {
  11103. src = '';
  11104. }
  11105. var ext = getFileExtension(src);
  11106. var mimetype = MimetypesKind[ext.toLowerCase()];
  11107. return mimetype || '';
  11108. };
  11109. /**
  11110. * Find the mime type of a given source string if possible. Uses the player
  11111. * source cache.
  11112. *
  11113. * @param {Player} player
  11114. * The player object
  11115. *
  11116. * @param {string} src
  11117. * The source string
  11118. *
  11119. * @return {string}
  11120. * The type that was found
  11121. */
  11122. var findMimetype = function findMimetype(player, src) {
  11123. if (!src) {
  11124. return '';
  11125. } // 1. check for the type in the `source` cache
  11126. if (player.cache_.source.src === src && player.cache_.source.type) {
  11127. return player.cache_.source.type;
  11128. } // 2. see if we have this source in our `currentSources` cache
  11129. var matchingSources = player.cache_.sources.filter(function (s) {
  11130. return s.src === src;
  11131. });
  11132. if (matchingSources.length) {
  11133. return matchingSources[0].type;
  11134. } // 3. look for the src url in source elements and use the type there
  11135. var sources = player.$$('source');
  11136. for (var i = 0; i < sources.length; i++) {
  11137. var s = sources[i];
  11138. if (s.type && s.src && s.src === src) {
  11139. return s.type;
  11140. }
  11141. } // 4. finally fallback to our list of mime types based on src url extension
  11142. return getMimetype(src);
  11143. };
  11144. /**
  11145. * @module filter-source
  11146. */
  11147. /**
  11148. * Filter out single bad source objects or multiple source objects in an
  11149. * array. Also flattens nested source object arrays into a 1 dimensional
  11150. * array of source objects.
  11151. *
  11152. * @param {Tech~SourceObject|Tech~SourceObject[]} src
  11153. * The src object to filter
  11154. *
  11155. * @return {Tech~SourceObject[]}
  11156. * An array of sourceobjects containing only valid sources
  11157. *
  11158. * @private
  11159. */
  11160. var filterSource = function filterSource(src) {
  11161. // traverse array
  11162. if (Array.isArray(src)) {
  11163. var newsrc = [];
  11164. src.forEach(function (srcobj) {
  11165. srcobj = filterSource(srcobj);
  11166. if (Array.isArray(srcobj)) {
  11167. newsrc = newsrc.concat(srcobj);
  11168. } else if (isObject(srcobj)) {
  11169. newsrc.push(srcobj);
  11170. }
  11171. });
  11172. src = newsrc;
  11173. } else if (typeof src === 'string' && src.trim()) {
  11174. // convert string into object
  11175. src = [fixSource({
  11176. src: src
  11177. })];
  11178. } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
  11179. // src is already valid
  11180. src = [fixSource(src)];
  11181. } else {
  11182. // invalid source, turn it into an empty array
  11183. src = [];
  11184. }
  11185. return src;
  11186. };
  11187. /**
  11188. * Checks src mimetype, adding it when possible
  11189. *
  11190. * @param {Tech~SourceObject} src
  11191. * The src object to check
  11192. * @return {Tech~SourceObject}
  11193. * src Object with known type
  11194. */
  11195. function fixSource(src) {
  11196. if (src.type) return src;
  11197. var mimetype = etMimetype(src.src);
  11198. if (!src.type && mimetype) {
  11199. src.type = mimetype;
  11200. }
  11201. return src;
  11202. }
  11203. /**
  11204. * The `MediaLoader` is the `Component` that decides which playback technology to load
  11205. * when a player is initialized.
  11206. *
  11207. * @extends Component
  11208. */
  11209. var MediaLoader =
  11210. /*#__PURE__*/
  11211. function (_Component) {
  11212. _inheritsLoose(MediaLoader, _Component);
  11213. /**
  11214. * Create an instance of this class.
  11215. *
  11216. * @param {Player} player
  11217. * The `Player` that this class should attach to.
  11218. *
  11219. * @param {Object} [options]
  11220. * The key/value store of player options.
  11221. *
  11222. * @param {Component~ReadyCallback} [ready]
  11223. * The function that is run when this component is ready.
  11224. */
  11225. function MediaLoader(player, options, ready) {
  11226. var _this;
  11227. // MediaLoader has no element
  11228. var options_ = mergeOptions({
  11229. createEl: false
  11230. }, options);
  11231. _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
  11232. // load the first supported playback technology.
  11233. if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
  11234. for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
  11235. var techName = toTitleCase(j[i]);
  11236. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  11237. // Remove once that deprecated behavior is removed.
  11238. if (!techName) {
  11239. tech = Component.getComponent(techName);
  11240. } // Check if the browser supports this technology
  11241. if (tech && tech.isSupported()) {
  11242. player.loadTech_(techName);
  11243. break;
  11244. }
  11245. }
  11246. } else {
  11247. // Loop through playback technologies (HTML5, Flash) and check for support.
  11248. // Then load the best source.
  11249. // A few assumptions here:
  11250. // All playback technologies respect preload false.
  11251. player.src(options.playerOptions.sources);
  11252. }
  11253. return _this;
  11254. }
  11255. return MediaLoader;
  11256. }(Component);
  11257. Component.registerComponent('MediaLoader', MediaLoader);
  11258. /**
  11259. * Component which is clickable or keyboard actionable, but is not a
  11260. * native HTML button.
  11261. *
  11262. * @extends Component
  11263. */
  11264. var ClickableComponent =
  11265. /*#__PURE__*/
  11266. function (_Component) {
  11267. _inheritsLoose(ClickableComponent, _Component);
  11268. /**
  11269. * Creates an instance of this class.
  11270. *
  11271. * @param {Player} player
  11272. * The `Player` that this class should be attached to.
  11273. *
  11274. * @param {Object} [options]
  11275. * The key/value store of player options.
  11276. */
  11277. function ClickableComponent(player, options) {
  11278. var _this;
  11279. _this = _Component.call(this, player, options) || this;
  11280. _this.emitTapEvents();
  11281. _this.enable();
  11282. return _this;
  11283. }
  11284. /**
  11285. * Create the `ClickableComponent`s DOM element.
  11286. *
  11287. * @param {string} [tag=div]
  11288. * The element's node type.
  11289. *
  11290. * @param {Object} [props={}]
  11291. * An object of properties that should be set on the element.
  11292. *
  11293. * @param {Object} [attributes={}]
  11294. * An object of attributes that should be set on the element.
  11295. *
  11296. * @return {Element}
  11297. * The element that gets created.
  11298. */
  11299. var _proto = ClickableComponent.prototype;
  11300. _proto.createEl = function createEl(tag, props, attributes) {
  11301. if (tag === void 0) {
  11302. tag = 'div';
  11303. }
  11304. if (props === void 0) {
  11305. props = {};
  11306. }
  11307. if (attributes === void 0) {
  11308. attributes = {};
  11309. }
  11310. props = assign({
  11311. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  11312. className: this.buildCSSClass(),
  11313. tabIndex: 0
  11314. }, props);
  11315. if (tag === 'button') {
  11316. log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
  11317. } // Add ARIA attributes for clickable element which is not a native HTML button
  11318. attributes = assign({
  11319. role: 'button'
  11320. }, attributes);
  11321. this.tabIndex_ = props.tabIndex;
  11322. var el = _Component.prototype.createEl.call(this, tag, props, attributes);
  11323. this.createControlTextEl(el);
  11324. return el;
  11325. };
  11326. _proto.dispose = function dispose() {
  11327. // remove controlTextEl_ on dispose
  11328. this.controlTextEl_ = null;
  11329. _Component.prototype.dispose.call(this);
  11330. }
  11331. /**
  11332. * Create a control text element on this `ClickableComponent`
  11333. *
  11334. * @param {Element} [el]
  11335. * Parent element for the control text.
  11336. *
  11337. * @return {Element}
  11338. * The control text element that gets created.
  11339. */
  11340. ;
  11341. _proto.createControlTextEl = function createControlTextEl(el) {
  11342. this.controlTextEl_ = createEl('span', {
  11343. className: 'vjs-control-text'
  11344. }, {
  11345. // let the screen reader user know that the text of the element may change
  11346. 'aria-live': 'polite'
  11347. });
  11348. if (el) {
  11349. el.appendChild(this.controlTextEl_);
  11350. }
  11351. this.controlText(this.controlText_, el);
  11352. return this.controlTextEl_;
  11353. }
  11354. /**
  11355. * Get or set the localize text to use for the controls on the `ClickableComponent`.
  11356. *
  11357. * @param {string} [text]
  11358. * Control text for element.
  11359. *
  11360. * @param {Element} [el=this.el()]
  11361. * Element to set the title on.
  11362. *
  11363. * @return {string}
  11364. * - The control text when getting
  11365. */
  11366. ;
  11367. _proto.controlText = function controlText(text, el) {
  11368. if (el === void 0) {
  11369. el = this.el();
  11370. }
  11371. if (text === undefined) {
  11372. return this.controlText_ || 'Need Text';
  11373. }
  11374. var localizedText = this.localize(text);
  11375. this.controlText_ = text;
  11376. textContent(this.controlTextEl_, localizedText);
  11377. if (!this.nonIconControl) {
  11378. // Set title attribute if only an icon is shown
  11379. el.setAttribute('title', localizedText);
  11380. }
  11381. }
  11382. /**
  11383. * Builds the default DOM `className`.
  11384. *
  11385. * @return {string}
  11386. * The DOM `className` for this object.
  11387. */
  11388. ;
  11389. _proto.buildCSSClass = function buildCSSClass() {
  11390. return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
  11391. }
  11392. /**
  11393. * Enable this `ClickableComponent`
  11394. */
  11395. ;
  11396. _proto.enable = function enable() {
  11397. if (!this.enabled_) {
  11398. this.enabled_ = true;
  11399. this.removeClass('vjs-disabled');
  11400. this.el_.setAttribute('aria-disabled', 'false');
  11401. if (typeof this.tabIndex_ !== 'undefined') {
  11402. this.el_.setAttribute('tabIndex', this.tabIndex_);
  11403. }
  11404. this.on(['tap', 'click'], this.handleClick);
  11405. this.on('keydown', this.handleKeyDown);
  11406. }
  11407. }
  11408. /**
  11409. * Disable this `ClickableComponent`
  11410. */
  11411. ;
  11412. _proto.disable = function disable() {
  11413. this.enabled_ = false;
  11414. this.addClass('vjs-disabled');
  11415. this.el_.setAttribute('aria-disabled', 'true');
  11416. if (typeof this.tabIndex_ !== 'undefined') {
  11417. this.el_.removeAttribute('tabIndex');
  11418. }
  11419. this.off(['tap', 'click'], this.handleClick);
  11420. this.off('keydown', this.handleKeyDown);
  11421. }
  11422. /**
  11423. * Event handler that is called when a `ClickableComponent` receives a
  11424. * `click` or `tap` event.
  11425. *
  11426. * @param {EventTarget~Event} event
  11427. * The `tap` or `click` event that caused this function to be called.
  11428. *
  11429. * @listens tap
  11430. * @listens click
  11431. * @abstract
  11432. */
  11433. ;
  11434. _proto.handleClick = function handleClick(event) {}
  11435. /**
  11436. * Event handler that is called when a `ClickableComponent` receives a
  11437. * `keydown` event.
  11438. *
  11439. * By default, if the key is Space or Enter, it will trigger a `click` event.
  11440. *
  11441. * @param {EventTarget~Event} event
  11442. * The `keydown` event that caused this function to be called.
  11443. *
  11444. * @listens keydown
  11445. */
  11446. ;
  11447. _proto.handleKeyDown = function handleKeyDown(event) {
  11448. // Support Space or Enter key operation to fire a click event. Also,
  11449. // prevent the event from propagating through the DOM and triggering
  11450. // Player hotkeys.
  11451. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  11452. event.preventDefault();
  11453. event.stopPropagation();
  11454. this.trigger('click');
  11455. } else {
  11456. // Pass keypress handling up for unsupported keys
  11457. _Component.prototype.handleKeyDown.call(this, event);
  11458. }
  11459. };
  11460. return ClickableComponent;
  11461. }(Component);
  11462. Component.registerComponent('ClickableComponent', ClickableComponent);
  11463. /**
  11464. * A `ClickableComponent` that handles showing the poster image for the player.
  11465. *
  11466. * @extends ClickableComponent
  11467. */
  11468. var PosterImage =
  11469. /*#__PURE__*/
  11470. function (_ClickableComponent) {
  11471. _inheritsLoose(PosterImage, _ClickableComponent);
  11472. /**
  11473. * Create an instance of this class.
  11474. *
  11475. * @param {Player} player
  11476. * The `Player` that this class should attach to.
  11477. *
  11478. * @param {Object} [options]
  11479. * The key/value store of player options.
  11480. */
  11481. function PosterImage(player, options) {
  11482. var _this;
  11483. _this = _ClickableComponent.call(this, player, options) || this;
  11484. _this.update();
  11485. player.on('posterchange', bind(_assertThisInitialized(_this), _this.update));
  11486. return _this;
  11487. }
  11488. /**
  11489. * Clean up and dispose of the `PosterImage`.
  11490. */
  11491. var _proto = PosterImage.prototype;
  11492. _proto.dispose = function dispose() {
  11493. this.player().off('posterchange', this.update);
  11494. _ClickableComponent.prototype.dispose.call(this);
  11495. }
  11496. /**
  11497. * Create the `PosterImage`s DOM element.
  11498. *
  11499. * @return {Element}
  11500. * The element that gets created.
  11501. */
  11502. ;
  11503. _proto.createEl = function createEl$1() {
  11504. var el = createEl('div', {
  11505. className: 'vjs-poster',
  11506. // Don't want poster to be tabbable.
  11507. tabIndex: -1
  11508. });
  11509. return el;
  11510. }
  11511. /**
  11512. * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
  11513. *
  11514. * @listens Player#posterchange
  11515. *
  11516. * @param {EventTarget~Event} [event]
  11517. * The `Player#posterchange` event that triggered this function.
  11518. */
  11519. ;
  11520. _proto.update = function update(event) {
  11521. var url = this.player().poster();
  11522. this.setSrc(url); // If there's no poster source we should display:none on this component
  11523. // so it's not still clickable or right-clickable
  11524. if (url) {
  11525. this.show();
  11526. } else {
  11527. this.hide();
  11528. }
  11529. }
  11530. /**
  11531. * Set the source of the `PosterImage` depending on the display method.
  11532. *
  11533. * @param {string} url
  11534. * The URL to the source for the `PosterImage`.
  11535. */
  11536. ;
  11537. _proto.setSrc = function setSrc(url) {
  11538. var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
  11539. // this will throw an extra error
  11540. if (url) {
  11541. backgroundImage = "url(\"" + url + "\")";
  11542. }
  11543. this.el_.style.backgroundImage = backgroundImage;
  11544. }
  11545. /**
  11546. * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
  11547. * {@link ClickableComponent#handleClick} for instances where this will be triggered.
  11548. *
  11549. * @listens tap
  11550. * @listens click
  11551. * @listens keydown
  11552. *
  11553. * @param {EventTarget~Event} event
  11554. + The `click`, `tap` or `keydown` event that caused this function to be called.
  11555. */
  11556. ;
  11557. _proto.handleClick = function handleClick(event) {
  11558. // We don't want a click to trigger playback when controls are disabled
  11559. if (!this.player_.controls()) {
  11560. return;
  11561. }
  11562. if (this.player_.tech(true)) {
  11563. this.player_.tech(true).focus();
  11564. }
  11565. if (this.player_.paused()) {
  11566. silencePromise(this.player_.play());
  11567. } else {
  11568. this.player_.pause();
  11569. }
  11570. };
  11571. return PosterImage;
  11572. }(ClickableComponent);
  11573. Component.registerComponent('PosterImage', PosterImage);
  11574. var darkGray = '#222';
  11575. var lightGray = '#ccc';
  11576. var fontMap = {
  11577. monospace: 'monospace',
  11578. sansSerif: 'sans-serif',
  11579. serif: 'serif',
  11580. monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
  11581. monospaceSerif: '"Courier New", monospace',
  11582. proportionalSansSerif: 'sans-serif',
  11583. proportionalSerif: 'serif',
  11584. casual: '"Comic Sans MS", Impact, fantasy',
  11585. script: '"Monotype Corsiva", cursive',
  11586. smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
  11587. };
  11588. /**
  11589. * Construct an rgba color from a given hex color code.
  11590. *
  11591. * @param {number} color
  11592. * Hex number for color, like #f0e or #f604e2.
  11593. *
  11594. * @param {number} opacity
  11595. * Value for opacity, 0.0 - 1.0.
  11596. *
  11597. * @return {string}
  11598. * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
  11599. */
  11600. function constructColor(color, opacity) {
  11601. var hex;
  11602. if (color.length === 4) {
  11603. // color looks like "#f0e"
  11604. hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
  11605. } else if (color.length === 7) {
  11606. // color looks like "#f604e2"
  11607. hex = color.slice(1);
  11608. } else {
  11609. throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
  11610. }
  11611. return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
  11612. }
  11613. /**
  11614. * Try to update the style of a DOM element. Some style changes will throw an error,
  11615. * particularly in IE8. Those should be noops.
  11616. *
  11617. * @param {Element} el
  11618. * The DOM element to be styled.
  11619. *
  11620. * @param {string} style
  11621. * The CSS property on the element that should be styled.
  11622. *
  11623. * @param {string} rule
  11624. * The style rule that should be applied to the property.
  11625. *
  11626. * @private
  11627. */
  11628. function tryUpdateStyle(el, style, rule) {
  11629. try {
  11630. el.style[style] = rule;
  11631. } catch (e) {
  11632. // Satisfies linter.
  11633. return;
  11634. }
  11635. }
  11636. /**
  11637. * The component for displaying text track cues.
  11638. *
  11639. * @extends Component
  11640. */
  11641. var TextTrackDisplay =
  11642. /*#__PURE__*/
  11643. function (_Component) {
  11644. _inheritsLoose(TextTrackDisplay, _Component);
  11645. /**
  11646. * Creates an instance of this class.
  11647. *
  11648. * @param {Player} player
  11649. * The `Player` that this class should be attached to.
  11650. *
  11651. * @param {Object} [options]
  11652. * The key/value store of player options.
  11653. *
  11654. * @param {Component~ReadyCallback} [ready]
  11655. * The function to call when `TextTrackDisplay` is ready.
  11656. */
  11657. function TextTrackDisplay(player, options, ready) {
  11658. var _this;
  11659. _this = _Component.call(this, player, options, ready) || this;
  11660. var updateDisplayHandler = bind(_assertThisInitialized(_this), _this.updateDisplay);
  11661. player.on('loadstart', bind(_assertThisInitialized(_this), _this.toggleDisplay));
  11662. player.on('texttrackchange', updateDisplayHandler);
  11663. player.on('loadedmetadata', bind(_assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error
  11664. // if a track should show by default and the display hadn't loaded yet.
  11665. // Should probably be moved to an external track loader when we support
  11666. // tracks that don't need a display.
  11667. player.ready(bind(_assertThisInitialized(_this), function () {
  11668. if (player.tech_ && player.tech_.featuresNativeTextTracks) {
  11669. this.hide();
  11670. return;
  11671. }
  11672. player.on('fullscreenchange', updateDisplayHandler);
  11673. player.on('playerresize', updateDisplayHandler);
  11674. window$1.addEventListener('orientationchange', updateDisplayHandler);
  11675. player.on('dispose', function () {
  11676. return window$1.removeEventListener('orientationchange', updateDisplayHandler);
  11677. });
  11678. var tracks = this.options_.playerOptions.tracks || [];
  11679. for (var i = 0; i < tracks.length; i++) {
  11680. this.player_.addRemoteTextTrack(tracks[i], true);
  11681. }
  11682. this.preselectTrack();
  11683. }));
  11684. return _this;
  11685. }
  11686. /**
  11687. * Preselect a track following this precedence:
  11688. * - matches the previously selected {@link TextTrack}'s language and kind
  11689. * - matches the previously selected {@link TextTrack}'s language only
  11690. * - is the first default captions track
  11691. * - is the first default descriptions track
  11692. *
  11693. * @listens Player#loadstart
  11694. */
  11695. var _proto = TextTrackDisplay.prototype;
  11696. _proto.preselectTrack = function preselectTrack() {
  11697. var modes = {
  11698. captions: 1,
  11699. subtitles: 1
  11700. };
  11701. var trackList = this.player_.textTracks();
  11702. var userPref = this.player_.cache_.selectedLanguage;
  11703. var firstDesc;
  11704. var firstCaptions;
  11705. var preferredTrack;
  11706. for (var i = 0; i < trackList.length; i++) {
  11707. var track = trackList[i];
  11708. if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
  11709. // Always choose the track that matches both language and kind
  11710. if (track.kind === userPref.kind) {
  11711. preferredTrack = track; // or choose the first track that matches language
  11712. } else if (!preferredTrack) {
  11713. preferredTrack = track;
  11714. } // clear everything if offTextTrackMenuItem was clicked
  11715. } else if (userPref && !userPref.enabled) {
  11716. preferredTrack = null;
  11717. firstDesc = null;
  11718. firstCaptions = null;
  11719. } else if (track["default"]) {
  11720. if (track.kind === 'descriptions' && !firstDesc) {
  11721. firstDesc = track;
  11722. } else if (track.kind in modes && !firstCaptions) {
  11723. firstCaptions = track;
  11724. }
  11725. }
  11726. } // The preferredTrack matches the user preference and takes
  11727. // precedence over all the other tracks.
  11728. // So, display the preferredTrack before the first default track
  11729. // and the subtitles/captions track before the descriptions track
  11730. if (preferredTrack) {
  11731. preferredTrack.mode = 'showing';
  11732. } else if (firstCaptions) {
  11733. firstCaptions.mode = 'showing';
  11734. } else if (firstDesc) {
  11735. firstDesc.mode = 'showing';
  11736. }
  11737. }
  11738. /**
  11739. * Turn display of {@link TextTrack}'s from the current state into the other state.
  11740. * There are only two states:
  11741. * - 'shown'
  11742. * - 'hidden'
  11743. *
  11744. * @listens Player#loadstart
  11745. */
  11746. ;
  11747. _proto.toggleDisplay = function toggleDisplay() {
  11748. if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
  11749. this.hide();
  11750. } else {
  11751. this.show();
  11752. }
  11753. }
  11754. /**
  11755. * Create the {@link Component}'s DOM element.
  11756. *
  11757. * @return {Element}
  11758. * The element that was created.
  11759. */
  11760. ;
  11761. _proto.createEl = function createEl() {
  11762. return _Component.prototype.createEl.call(this, 'div', {
  11763. className: 'vjs-text-track-display'
  11764. }, {
  11765. 'aria-live': 'off',
  11766. 'aria-atomic': 'true'
  11767. });
  11768. }
  11769. /**
  11770. * Clear all displayed {@link TextTrack}s.
  11771. */
  11772. ;
  11773. _proto.clearDisplay = function clearDisplay() {
  11774. if (typeof window$1.WebVTT === 'function') {
  11775. window$1.WebVTT.processCues(window$1, [], this.el_);
  11776. }
  11777. }
  11778. /**
  11779. * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
  11780. * a {@link Player#fullscreenchange} is fired.
  11781. *
  11782. * @listens Player#texttrackchange
  11783. * @listens Player#fullscreenchange
  11784. */
  11785. ;
  11786. _proto.updateDisplay = function updateDisplay() {
  11787. var tracks = this.player_.textTracks();
  11788. var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
  11789. this.clearDisplay();
  11790. if (allowMultipleShowingTracks) {
  11791. var showingTracks = [];
  11792. for (var _i = 0; _i < tracks.length; ++_i) {
  11793. var track = tracks[_i];
  11794. if (track.mode !== 'showing') {
  11795. continue;
  11796. }
  11797. showingTracks.push(track);
  11798. }
  11799. this.updateForTrack(showingTracks);
  11800. return;
  11801. } // Track display prioritization model: if multiple tracks are 'showing',
  11802. // display the first 'subtitles' or 'captions' track which is 'showing',
  11803. // otherwise display the first 'descriptions' track which is 'showing'
  11804. var descriptionsTrack = null;
  11805. var captionsSubtitlesTrack = null;
  11806. var i = tracks.length;
  11807. while (i--) {
  11808. var _track = tracks[i];
  11809. if (_track.mode === 'showing') {
  11810. if (_track.kind === 'descriptions') {
  11811. descriptionsTrack = _track;
  11812. } else {
  11813. captionsSubtitlesTrack = _track;
  11814. }
  11815. }
  11816. }
  11817. if (captionsSubtitlesTrack) {
  11818. if (this.getAttribute('aria-live') !== 'off') {
  11819. this.setAttribute('aria-live', 'off');
  11820. }
  11821. this.updateForTrack(captionsSubtitlesTrack);
  11822. } else if (descriptionsTrack) {
  11823. if (this.getAttribute('aria-live') !== 'assertive') {
  11824. this.setAttribute('aria-live', 'assertive');
  11825. }
  11826. this.updateForTrack(descriptionsTrack);
  11827. }
  11828. }
  11829. /**
  11830. * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
  11831. *
  11832. * @param {TextTrack} track
  11833. * Text track object containing active cues to style.
  11834. */
  11835. ;
  11836. _proto.updateDisplayState = function updateDisplayState(track) {
  11837. var overrides = this.player_.textTrackSettings.getValues();
  11838. var cues = track.activeCues;
  11839. var i = cues.length;
  11840. while (i--) {
  11841. var cue = cues[i];
  11842. if (!cue) {
  11843. continue;
  11844. }
  11845. var cueDiv = cue.displayState;
  11846. if (overrides.color) {
  11847. cueDiv.firstChild.style.color = overrides.color;
  11848. }
  11849. if (overrides.textOpacity) {
  11850. tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
  11851. }
  11852. if (overrides.backgroundColor) {
  11853. cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
  11854. }
  11855. if (overrides.backgroundOpacity) {
  11856. tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
  11857. }
  11858. if (overrides.windowColor) {
  11859. if (overrides.windowOpacity) {
  11860. tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
  11861. } else {
  11862. cueDiv.style.backgroundColor = overrides.windowColor;
  11863. }
  11864. }
  11865. if (overrides.edgeStyle) {
  11866. if (overrides.edgeStyle === 'dropshadow') {
  11867. cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
  11868. } else if (overrides.edgeStyle === 'raised') {
  11869. cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
  11870. } else if (overrides.edgeStyle === 'depressed') {
  11871. cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
  11872. } else if (overrides.edgeStyle === 'uniform') {
  11873. cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
  11874. }
  11875. }
  11876. if (overrides.fontPercent && overrides.fontPercent !== 1) {
  11877. var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
  11878. cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
  11879. cueDiv.style.height = 'auto';
  11880. cueDiv.style.top = 'auto';
  11881. cueDiv.style.bottom = '2px';
  11882. }
  11883. if (overrides.fontFamily && overrides.fontFamily !== 'default') {
  11884. if (overrides.fontFamily === 'small-caps') {
  11885. cueDiv.firstChild.style.fontVariant = 'small-caps';
  11886. } else {
  11887. cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
  11888. }
  11889. }
  11890. }
  11891. }
  11892. /**
  11893. * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
  11894. *
  11895. * @param {TextTrack|TextTrack[]} tracks
  11896. * Text track object or text track array to be added to the list.
  11897. */
  11898. ;
  11899. _proto.updateForTrack = function updateForTrack(tracks) {
  11900. if (!Array.isArray(tracks)) {
  11901. tracks = [tracks];
  11902. }
  11903. if (typeof window$1.WebVTT !== 'function' || tracks.every(function (track) {
  11904. return !track.activeCues;
  11905. })) {
  11906. return;
  11907. }
  11908. var cues = []; // push all active track cues
  11909. for (var i = 0; i < tracks.length; ++i) {
  11910. var track = tracks[i];
  11911. for (var j = 0; j < track.activeCues.length; ++j) {
  11912. cues.push(track.activeCues[j]);
  11913. }
  11914. } // removes all cues before it processes new ones
  11915. window$1.WebVTT.processCues(window$1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary
  11916. for (var _i2 = 0; _i2 < tracks.length; ++_i2) {
  11917. var _track2 = tracks[_i2];
  11918. for (var _j = 0; _j < _track2.activeCues.length; ++_j) {
  11919. var cueEl = _track2.activeCues[_j].displayState;
  11920. addClass(cueEl, 'vjs-text-track-cue');
  11921. addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2));
  11922. }
  11923. if (this.player_.textTrackSettings) {
  11924. this.updateDisplayState(_track2);
  11925. }
  11926. }
  11927. };
  11928. return TextTrackDisplay;
  11929. }(Component);
  11930. Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
  11931. /**
  11932. * A loading spinner for use during waiting/loading events.
  11933. *
  11934. * @extends Component
  11935. */
  11936. var LoadingSpinner =
  11937. /*#__PURE__*/
  11938. function (_Component) {
  11939. _inheritsLoose(LoadingSpinner, _Component);
  11940. function LoadingSpinner() {
  11941. return _Component.apply(this, arguments) || this;
  11942. }
  11943. var _proto = LoadingSpinner.prototype;
  11944. /**
  11945. * Create the `LoadingSpinner`s DOM element.
  11946. *
  11947. * @return {Element}
  11948. * The dom element that gets created.
  11949. */
  11950. _proto.createEl = function createEl$1() {
  11951. var isAudio = this.player_.isAudio();
  11952. var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
  11953. var controlText = createEl('span', {
  11954. className: 'vjs-control-text',
  11955. innerHTML: this.localize('{1} is loading.', [playerType])
  11956. });
  11957. var el = _Component.prototype.createEl.call(this, 'div', {
  11958. className: 'vjs-loading-spinner',
  11959. dir: 'ltr'
  11960. });
  11961. el.appendChild(controlText);
  11962. return el;
  11963. };
  11964. return LoadingSpinner;
  11965. }(Component);
  11966. Component.registerComponent('LoadingSpinner', LoadingSpinner);
  11967. /**
  11968. * Base class for all buttons.
  11969. *
  11970. * @extends ClickableComponent
  11971. */
  11972. var Button =
  11973. /*#__PURE__*/
  11974. function (_ClickableComponent) {
  11975. _inheritsLoose(Button, _ClickableComponent);
  11976. function Button() {
  11977. return _ClickableComponent.apply(this, arguments) || this;
  11978. }
  11979. var _proto = Button.prototype;
  11980. /**
  11981. * Create the `Button`s DOM element.
  11982. *
  11983. * @param {string} [tag="button"]
  11984. * The element's node type. This argument is IGNORED: no matter what
  11985. * is passed, it will always create a `button` element.
  11986. *
  11987. * @param {Object} [props={}]
  11988. * An object of properties that should be set on the element.
  11989. *
  11990. * @param {Object} [attributes={}]
  11991. * An object of attributes that should be set on the element.
  11992. *
  11993. * @return {Element}
  11994. * The element that gets created.
  11995. */
  11996. _proto.createEl = function createEl(tag, props, attributes) {
  11997. if (props === void 0) {
  11998. props = {};
  11999. }
  12000. if (attributes === void 0) {
  12001. attributes = {};
  12002. }
  12003. tag = 'button';
  12004. props = assign({
  12005. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  12006. className: this.buildCSSClass()
  12007. }, props); // Add attributes for button element
  12008. attributes = assign({
  12009. // Necessary since the default button type is "submit"
  12010. type: 'button'
  12011. }, attributes);
  12012. var el = Component.prototype.createEl.call(this, tag, props, attributes);
  12013. this.createControlTextEl(el);
  12014. return el;
  12015. }
  12016. /**
  12017. * Add a child `Component` inside of this `Button`.
  12018. *
  12019. * @param {string|Component} child
  12020. * The name or instance of a child to add.
  12021. *
  12022. * @param {Object} [options={}]
  12023. * The key/value store of options that will get passed to children of
  12024. * the child.
  12025. *
  12026. * @return {Component}
  12027. * The `Component` that gets added as a child. When using a string the
  12028. * `Component` will get created by this process.
  12029. *
  12030. * @deprecated since version 5
  12031. */
  12032. ;
  12033. _proto.addChild = function addChild(child, options) {
  12034. if (options === void 0) {
  12035. options = {};
  12036. }
  12037. var className = this.constructor.name;
  12038. 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
  12039. return Component.prototype.addChild.call(this, child, options);
  12040. }
  12041. /**
  12042. * Enable the `Button` element so that it can be activated or clicked. Use this with
  12043. * {@link Button#disable}.
  12044. */
  12045. ;
  12046. _proto.enable = function enable() {
  12047. _ClickableComponent.prototype.enable.call(this);
  12048. this.el_.removeAttribute('disabled');
  12049. }
  12050. /**
  12051. * Disable the `Button` element so that it cannot be activated or clicked. Use this with
  12052. * {@link Button#enable}.
  12053. */
  12054. ;
  12055. _proto.disable = function disable() {
  12056. _ClickableComponent.prototype.disable.call(this);
  12057. this.el_.setAttribute('disabled', 'disabled');
  12058. }
  12059. /**
  12060. * This gets called when a `Button` has focus and `keydown` is triggered via a key
  12061. * press.
  12062. *
  12063. * @param {EventTarget~Event} event
  12064. * The event that caused this function to get called.
  12065. *
  12066. * @listens keydown
  12067. */
  12068. ;
  12069. _proto.handleKeyDown = function handleKeyDown(event) {
  12070. // Ignore Space or Enter key operation, which is handled by the browser for
  12071. // a button - though not for its super class, ClickableComponent. Also,
  12072. // prevent the event from propagating through the DOM and triggering Player
  12073. // hotkeys. We do not preventDefault here because we _want_ the browser to
  12074. // handle it.
  12075. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  12076. event.stopPropagation();
  12077. return;
  12078. } // Pass keypress handling up for unsupported keys
  12079. _ClickableComponent.prototype.handleKeyDown.call(this, event);
  12080. };
  12081. return Button;
  12082. }(ClickableComponent);
  12083. Component.registerComponent('Button', Button);
  12084. /**
  12085. * The initial play button that shows before the video has played. The hiding of the
  12086. * `BigPlayButton` get done via CSS and `Player` states.
  12087. *
  12088. * @extends Button
  12089. */
  12090. var BigPlayButton =
  12091. /*#__PURE__*/
  12092. function (_Button) {
  12093. _inheritsLoose(BigPlayButton, _Button);
  12094. function BigPlayButton(player, options) {
  12095. var _this;
  12096. _this = _Button.call(this, player, options) || this;
  12097. _this.mouseused_ = false;
  12098. _this.on('mousedown', _this.handleMouseDown);
  12099. return _this;
  12100. }
  12101. /**
  12102. * Builds the default DOM `className`.
  12103. *
  12104. * @return {string}
  12105. * The DOM `className` for this object. Always returns 'vjs-big-play-button'.
  12106. */
  12107. var _proto = BigPlayButton.prototype;
  12108. _proto.buildCSSClass = function buildCSSClass() {
  12109. return 'vjs-big-play-button';
  12110. }
  12111. /**
  12112. * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
  12113. * for more detailed information on what a click can be.
  12114. *
  12115. * @param {EventTarget~Event} event
  12116. * The `keydown`, `tap`, or `click` event that caused this function to be
  12117. * called.
  12118. *
  12119. * @listens tap
  12120. * @listens click
  12121. */
  12122. ;
  12123. _proto.handleClick = function handleClick(event) {
  12124. var playPromise = this.player_.play(); // exit early if clicked via the mouse
  12125. if (this.mouseused_ && event.clientX && event.clientY) {
  12126. silencePromise(playPromise);
  12127. if (this.player_.tech(true)) {
  12128. this.player_.tech(true).focus();
  12129. }
  12130. return;
  12131. }
  12132. var cb = this.player_.getChild('controlBar');
  12133. var playToggle = cb && cb.getChild('playToggle');
  12134. if (!playToggle) {
  12135. this.player_.tech(true).focus();
  12136. return;
  12137. }
  12138. var playFocus = function playFocus() {
  12139. return playToggle.focus();
  12140. };
  12141. if (isPromise(playPromise)) {
  12142. playPromise.then(playFocus, function () {});
  12143. } else {
  12144. this.setTimeout(playFocus, 1);
  12145. }
  12146. };
  12147. _proto.handleKeyDown = function handleKeyDown(event) {
  12148. this.mouseused_ = false;
  12149. _Button.prototype.handleKeyDown.call(this, event);
  12150. };
  12151. _proto.handleMouseDown = function handleMouseDown(event) {
  12152. this.mouseused_ = true;
  12153. };
  12154. return BigPlayButton;
  12155. }(Button);
  12156. /**
  12157. * The text that should display over the `BigPlayButton`s controls. Added to for localization.
  12158. *
  12159. * @type {string}
  12160. * @private
  12161. */
  12162. BigPlayButton.prototype.controlText_ = 'Play Video';
  12163. Component.registerComponent('BigPlayButton', BigPlayButton);
  12164. /**
  12165. * The `CloseButton` is a `{@link Button}` that fires a `close` event when
  12166. * it gets clicked.
  12167. *
  12168. * @extends Button
  12169. */
  12170. var CloseButton =
  12171. /*#__PURE__*/
  12172. function (_Button) {
  12173. _inheritsLoose(CloseButton, _Button);
  12174. /**
  12175. * Creates an instance of the this class.
  12176. *
  12177. * @param {Player} player
  12178. * The `Player` that this class should be attached to.
  12179. *
  12180. * @param {Object} [options]
  12181. * The key/value store of player options.
  12182. */
  12183. function CloseButton(player, options) {
  12184. var _this;
  12185. _this = _Button.call(this, player, options) || this;
  12186. _this.controlText(options && options.controlText || _this.localize('Close'));
  12187. return _this;
  12188. }
  12189. /**
  12190. * Builds the default DOM `className`.
  12191. *
  12192. * @return {string}
  12193. * The DOM `className` for this object.
  12194. */
  12195. var _proto = CloseButton.prototype;
  12196. _proto.buildCSSClass = function buildCSSClass() {
  12197. return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
  12198. }
  12199. /**
  12200. * This gets called when a `CloseButton` gets clicked. See
  12201. * {@link ClickableComponent#handleClick} for more information on when
  12202. * this will be triggered
  12203. *
  12204. * @param {EventTarget~Event} event
  12205. * The `keydown`, `tap`, or `click` event that caused this function to be
  12206. * called.
  12207. *
  12208. * @listens tap
  12209. * @listens click
  12210. * @fires CloseButton#close
  12211. */
  12212. ;
  12213. _proto.handleClick = function handleClick(event) {
  12214. /**
  12215. * Triggered when the a `CloseButton` is clicked.
  12216. *
  12217. * @event CloseButton#close
  12218. * @type {EventTarget~Event}
  12219. *
  12220. * @property {boolean} [bubbles=false]
  12221. * set to false so that the close event does not
  12222. * bubble up to parents if there is no listener
  12223. */
  12224. this.trigger({
  12225. type: 'close',
  12226. bubbles: false
  12227. });
  12228. }
  12229. /**
  12230. * Event handler that is called when a `CloseButton` receives a
  12231. * `keydown` event.
  12232. *
  12233. * By default, if the key is Esc, it will trigger a `click` event.
  12234. *
  12235. * @param {EventTarget~Event} event
  12236. * The `keydown` event that caused this function to be called.
  12237. *
  12238. * @listens keydown
  12239. */
  12240. ;
  12241. _proto.handleKeyDown = function handleKeyDown(event) {
  12242. // Esc button will trigger `click` event
  12243. if (keycode.isEventKey(event, 'Esc')) {
  12244. event.preventDefault();
  12245. event.stopPropagation();
  12246. this.trigger('click');
  12247. } else {
  12248. // Pass keypress handling up for unsupported keys
  12249. _Button.prototype.handleKeyDown.call(this, event);
  12250. }
  12251. };
  12252. return CloseButton;
  12253. }(Button);
  12254. Component.registerComponent('CloseButton', CloseButton);
  12255. /**
  12256. * Button to toggle between play and pause.
  12257. *
  12258. * @extends Button
  12259. */
  12260. var PlayToggle =
  12261. /*#__PURE__*/
  12262. function (_Button) {
  12263. _inheritsLoose(PlayToggle, _Button);
  12264. /**
  12265. * Creates an instance of this class.
  12266. *
  12267. * @param {Player} player
  12268. * The `Player` that this class should be attached to.
  12269. *
  12270. * @param {Object} [options={}]
  12271. * The key/value store of player options.
  12272. */
  12273. function PlayToggle(player, options) {
  12274. var _this;
  12275. if (options === void 0) {
  12276. options = {};
  12277. }
  12278. _this = _Button.call(this, player, options) || this; // show or hide replay icon
  12279. options.replay = options.replay === undefined || options.replay;
  12280. _this.on(player, 'play', _this.handlePlay);
  12281. _this.on(player, 'pause', _this.handlePause);
  12282. if (options.replay) {
  12283. _this.on(player, 'ended', _this.handleEnded);
  12284. }
  12285. return _this;
  12286. }
  12287. /**
  12288. * Builds the default DOM `className`.
  12289. *
  12290. * @return {string}
  12291. * The DOM `className` for this object.
  12292. */
  12293. var _proto = PlayToggle.prototype;
  12294. _proto.buildCSSClass = function buildCSSClass() {
  12295. return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
  12296. }
  12297. /**
  12298. * This gets called when an `PlayToggle` is "clicked". See
  12299. * {@link ClickableComponent} for more detailed information on what a click can be.
  12300. *
  12301. * @param {EventTarget~Event} [event]
  12302. * The `keydown`, `tap`, or `click` event that caused this function to be
  12303. * called.
  12304. *
  12305. * @listens tap
  12306. * @listens click
  12307. */
  12308. ;
  12309. _proto.handleClick = function handleClick(event) {
  12310. if (this.player_.paused()) {
  12311. this.player_.play();
  12312. } else {
  12313. this.player_.pause();
  12314. }
  12315. event.stopPropagation();
  12316. }
  12317. /**
  12318. * This gets called once after the video has ended and the user seeks so that
  12319. * we can change the replay button back to a play button.
  12320. *
  12321. * @param {EventTarget~Event} [event]
  12322. * The event that caused this function to run.
  12323. *
  12324. * @listens Player#seeked
  12325. */
  12326. ;
  12327. _proto.handleSeeked = function handleSeeked(event) {
  12328. this.removeClass('vjs-ended');
  12329. if (this.player_.paused()) {
  12330. this.handlePause(event);
  12331. } else {
  12332. this.handlePlay(event);
  12333. }
  12334. }
  12335. /**
  12336. * Add the vjs-playing class to the element so it can change appearance.
  12337. *
  12338. * @param {EventTarget~Event} [event]
  12339. * The event that caused this function to run.
  12340. *
  12341. * @listens Player#play
  12342. */
  12343. ;
  12344. _proto.handlePlay = function handlePlay(event) {
  12345. this.removeClass('vjs-ended');
  12346. this.removeClass('vjs-paused');
  12347. this.addClass('vjs-playing'); // change the button text to "Pause"
  12348. this.controlText('Pause');
  12349. }
  12350. /**
  12351. * Add the vjs-paused class to the element so it can change appearance.
  12352. *
  12353. * @param {EventTarget~Event} [event]
  12354. * The event that caused this function to run.
  12355. *
  12356. * @listens Player#pause
  12357. */
  12358. ;
  12359. _proto.handlePause = function handlePause(event) {
  12360. this.removeClass('vjs-playing');
  12361. this.addClass('vjs-paused'); // change the button text to "Play"
  12362. this.controlText('Play');
  12363. }
  12364. /**
  12365. * Add the vjs-ended class to the element so it can change appearance
  12366. *
  12367. * @param {EventTarget~Event} [event]
  12368. * The event that caused this function to run.
  12369. *
  12370. * @listens Player#ended
  12371. */
  12372. ;
  12373. _proto.handleEnded = function handleEnded(event) {
  12374. this.removeClass('vjs-playing');
  12375. this.addClass('vjs-ended'); // change the button text to "Replay"
  12376. this.controlText('Replay'); // on the next seek remove the replay button
  12377. this.one(this.player_, 'seeked', this.handleSeeked);
  12378. };
  12379. return PlayToggle;
  12380. }(Button);
  12381. /**
  12382. * The text that should display over the `PlayToggle`s controls. Added for localization.
  12383. *
  12384. * @type {string}
  12385. * @private
  12386. */
  12387. PlayToggle.prototype.controlText_ = 'Play';
  12388. Component.registerComponent('PlayToggle', PlayToggle);
  12389. /**
  12390. * @file format-time.js
  12391. * @module format-time
  12392. */
  12393. /**
  12394. * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
  12395. * seconds) will force a number of leading zeros to cover the length of the
  12396. * guide.
  12397. *
  12398. * @private
  12399. * @param {number} seconds
  12400. * Number of seconds to be turned into a string
  12401. *
  12402. * @param {number} guide
  12403. * Number (in seconds) to model the string after
  12404. *
  12405. * @return {string}
  12406. * Time formatted as H:MM:SS or M:SS
  12407. */
  12408. var defaultImplementation = function defaultImplementation(seconds, guide) {
  12409. seconds = seconds < 0 ? 0 : seconds;
  12410. var s = Math.floor(seconds % 60);
  12411. var m = Math.floor(seconds / 60 % 60);
  12412. var h = Math.floor(seconds / 3600);
  12413. var gm = Math.floor(guide / 60 % 60);
  12414. var gh = Math.floor(guide / 3600); // handle invalid times
  12415. if (isNaN(seconds) || seconds === Infinity) {
  12416. // '-' is false for all relational operators (e.g. <, >=) so this setting
  12417. // will add the minimum number of fields specified by the guide
  12418. h = m = s = '-';
  12419. } // Check if we need to show hours
  12420. h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
  12421. // Always show at least one digit of minutes.
  12422. m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
  12423. s = s < 10 ? '0' + s : s;
  12424. return h + m + s;
  12425. }; // Internal pointer to the current implementation.
  12426. var implementation$3 = defaultImplementation;
  12427. /**
  12428. * Replaces the default formatTime implementation with a custom implementation.
  12429. *
  12430. * @param {Function} customImplementation
  12431. * A function which will be used in place of the default formatTime
  12432. * implementation. Will receive the current time in seconds and the
  12433. * guide (in seconds) as arguments.
  12434. */
  12435. function setFormatTime(customImplementation) {
  12436. implementation$3 = customImplementation;
  12437. }
  12438. /**
  12439. * Resets formatTime to the default implementation.
  12440. */
  12441. function resetFormatTime() {
  12442. implementation$3 = defaultImplementation;
  12443. }
  12444. /**
  12445. * Delegates to either the default time formatting function or a custom
  12446. * function supplied via `setFormatTime`.
  12447. *
  12448. * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
  12449. * guide (in seconds) will force a number of leading zeros to cover the
  12450. * length of the guide.
  12451. *
  12452. * @static
  12453. * @example formatTime(125, 600) === "02:05"
  12454. * @param {number} seconds
  12455. * Number of seconds to be turned into a string
  12456. *
  12457. * @param {number} guide
  12458. * Number (in seconds) to model the string after
  12459. *
  12460. * @return {string}
  12461. * Time formatted as H:MM:SS or M:SS
  12462. */
  12463. function formatTime(seconds, guide) {
  12464. if (guide === void 0) {
  12465. guide = seconds;
  12466. }
  12467. return implementation$3(seconds, guide);
  12468. }
  12469. /**
  12470. * Displays time information about the video
  12471. *
  12472. * @extends Component
  12473. */
  12474. var TimeDisplay =
  12475. /*#__PURE__*/
  12476. function (_Component) {
  12477. _inheritsLoose(TimeDisplay, _Component);
  12478. /**
  12479. * Creates an instance of this class.
  12480. *
  12481. * @param {Player} player
  12482. * The `Player` that this class should be attached to.
  12483. *
  12484. * @param {Object} [options]
  12485. * The key/value store of player options.
  12486. */
  12487. function TimeDisplay(player, options) {
  12488. var _this;
  12489. _this = _Component.call(this, player, options) || this;
  12490. _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_this), _this.updateContent), 25);
  12491. _this.on(player, 'timeupdate', _this.throttledUpdateContent);
  12492. return _this;
  12493. }
  12494. /**
  12495. * Create the `Component`'s DOM element
  12496. *
  12497. * @return {Element}
  12498. * The element that was created.
  12499. */
  12500. var _proto = TimeDisplay.prototype;
  12501. _proto.createEl = function createEl$1() {
  12502. var className = this.buildCSSClass();
  12503. var el = _Component.prototype.createEl.call(this, 'div', {
  12504. className: className + " vjs-time-control vjs-control",
  12505. innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>"
  12506. });
  12507. this.contentEl_ = createEl('span', {
  12508. className: className + "-display"
  12509. }, {
  12510. // tell screen readers not to automatically read the time as it changes
  12511. 'aria-live': 'off',
  12512. // span elements have no implicit role, but some screen readers (notably VoiceOver)
  12513. // treat them as a break between items in the DOM when using arrow keys
  12514. // (or left-to-right swipes on iOS) to read contents of a page. Using
  12515. // role='presentation' causes VoiceOver to NOT treat this span as a break.
  12516. 'role': 'presentation'
  12517. });
  12518. this.updateTextNode_();
  12519. el.appendChild(this.contentEl_);
  12520. return el;
  12521. };
  12522. _proto.dispose = function dispose() {
  12523. this.contentEl_ = null;
  12524. this.textNode_ = null;
  12525. _Component.prototype.dispose.call(this);
  12526. }
  12527. /**
  12528. * Updates the "remaining time" text node with new content using the
  12529. * contents of the `formattedTime_` property.
  12530. *
  12531. * @private
  12532. */
  12533. ;
  12534. _proto.updateTextNode_ = function updateTextNode_() {
  12535. if (!this.contentEl_) {
  12536. return;
  12537. }
  12538. while (this.contentEl_.firstChild) {
  12539. this.contentEl_.removeChild(this.contentEl_.firstChild);
  12540. }
  12541. this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0));
  12542. this.contentEl_.appendChild(this.textNode_);
  12543. }
  12544. /**
  12545. * Generates a formatted time for this component to use in display.
  12546. *
  12547. * @param {number} time
  12548. * A numeric time, in seconds.
  12549. *
  12550. * @return {string}
  12551. * A formatted time
  12552. *
  12553. * @private
  12554. */
  12555. ;
  12556. _proto.formatTime_ = function formatTime_(time) {
  12557. return formatTime(time);
  12558. }
  12559. /**
  12560. * Updates the time display text node if it has what was passed in changed
  12561. * the formatted time.
  12562. *
  12563. * @param {number} time
  12564. * The time to update to
  12565. *
  12566. * @private
  12567. */
  12568. ;
  12569. _proto.updateFormattedTime_ = function updateFormattedTime_(time) {
  12570. var formattedTime = this.formatTime_(time);
  12571. if (formattedTime === this.formattedTime_) {
  12572. return;
  12573. }
  12574. this.formattedTime_ = formattedTime;
  12575. this.requestAnimationFrame(this.updateTextNode_);
  12576. }
  12577. /**
  12578. * To be filled out in the child class, should update the displayed time
  12579. * in accordance with the fact that the current time has changed.
  12580. *
  12581. * @param {EventTarget~Event} [event]
  12582. * The `timeupdate` event that caused this to run.
  12583. *
  12584. * @listens Player#timeupdate
  12585. */
  12586. ;
  12587. _proto.updateContent = function updateContent(event) {};
  12588. return TimeDisplay;
  12589. }(Component);
  12590. /**
  12591. * The text that is added to the `TimeDisplay` for screen reader users.
  12592. *
  12593. * @type {string}
  12594. * @private
  12595. */
  12596. TimeDisplay.prototype.labelText_ = 'Time';
  12597. /**
  12598. * The text that should display over the `TimeDisplay`s controls. Added to for localization.
  12599. *
  12600. * @type {string}
  12601. * @private
  12602. *
  12603. * @deprecated in v7; controlText_ is not used in non-active display Components
  12604. */
  12605. TimeDisplay.prototype.controlText_ = 'Time';
  12606. Component.registerComponent('TimeDisplay', TimeDisplay);
  12607. /**
  12608. * Displays the current time
  12609. *
  12610. * @extends Component
  12611. */
  12612. var CurrentTimeDisplay =
  12613. /*#__PURE__*/
  12614. function (_TimeDisplay) {
  12615. _inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
  12616. /**
  12617. * Creates an instance of this class.
  12618. *
  12619. * @param {Player} player
  12620. * The `Player` that this class should be attached to.
  12621. *
  12622. * @param {Object} [options]
  12623. * The key/value store of player options.
  12624. */
  12625. function CurrentTimeDisplay(player, options) {
  12626. var _this;
  12627. _this = _TimeDisplay.call(this, player, options) || this;
  12628. _this.on(player, 'ended', _this.handleEnded);
  12629. return _this;
  12630. }
  12631. /**
  12632. * Builds the default DOM `className`.
  12633. *
  12634. * @return {string}
  12635. * The DOM `className` for this object.
  12636. */
  12637. var _proto = CurrentTimeDisplay.prototype;
  12638. _proto.buildCSSClass = function buildCSSClass() {
  12639. return 'vjs-current-time';
  12640. }
  12641. /**
  12642. * Update current time display
  12643. *
  12644. * @param {EventTarget~Event} [event]
  12645. * The `timeupdate` event that caused this function to run.
  12646. *
  12647. * @listens Player#timeupdate
  12648. */
  12649. ;
  12650. _proto.updateContent = function updateContent(event) {
  12651. // Allows for smooth scrubbing, when player can't keep up.
  12652. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  12653. this.updateFormattedTime_(time);
  12654. }
  12655. /**
  12656. * When the player fires ended there should be no time left. Sadly
  12657. * this is not always the case, lets make it seem like that is the case
  12658. * for users.
  12659. *
  12660. * @param {EventTarget~Event} [event]
  12661. * The `ended` event that caused this to run.
  12662. *
  12663. * @listens Player#ended
  12664. */
  12665. ;
  12666. _proto.handleEnded = function handleEnded(event) {
  12667. if (!this.player_.duration()) {
  12668. return;
  12669. }
  12670. this.updateFormattedTime_(this.player_.duration());
  12671. };
  12672. return CurrentTimeDisplay;
  12673. }(TimeDisplay);
  12674. /**
  12675. * The text that is added to the `CurrentTimeDisplay` for screen reader users.
  12676. *
  12677. * @type {string}
  12678. * @private
  12679. */
  12680. CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
  12681. /**
  12682. * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
  12683. *
  12684. * @type {string}
  12685. * @private
  12686. *
  12687. * @deprecated in v7; controlText_ is not used in non-active display Components
  12688. */
  12689. CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
  12690. Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
  12691. /**
  12692. * Displays the duration
  12693. *
  12694. * @extends Component
  12695. */
  12696. var DurationDisplay =
  12697. /*#__PURE__*/
  12698. function (_TimeDisplay) {
  12699. _inheritsLoose(DurationDisplay, _TimeDisplay);
  12700. /**
  12701. * Creates an instance of this class.
  12702. *
  12703. * @param {Player} player
  12704. * The `Player` that this class should be attached to.
  12705. *
  12706. * @param {Object} [options]
  12707. * The key/value store of player options.
  12708. */
  12709. function DurationDisplay(player, options) {
  12710. var _this;
  12711. _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes,
  12712. // as they should always display the changed duration as
  12713. // it has changed
  12714. _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
  12715. // but the durationchange on the user agent will not fire.
  12716. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  12717. _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
  12718. // listeners could have broken dependent applications/libraries. These
  12719. // can likely be removed for 7.0.
  12720. _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
  12721. return _this;
  12722. }
  12723. /**
  12724. * Builds the default DOM `className`.
  12725. *
  12726. * @return {string}
  12727. * The DOM `className` for this object.
  12728. */
  12729. var _proto = DurationDisplay.prototype;
  12730. _proto.buildCSSClass = function buildCSSClass() {
  12731. return 'vjs-duration';
  12732. }
  12733. /**
  12734. * Update duration time display.
  12735. *
  12736. * @param {EventTarget~Event} [event]
  12737. * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
  12738. * this function to be called.
  12739. *
  12740. * @listens Player#durationchange
  12741. * @listens Player#timeupdate
  12742. * @listens Player#loadedmetadata
  12743. */
  12744. ;
  12745. _proto.updateContent = function updateContent(event) {
  12746. var duration = this.player_.duration();
  12747. if (this.duration_ !== duration) {
  12748. this.duration_ = duration;
  12749. this.updateFormattedTime_(duration);
  12750. }
  12751. };
  12752. return DurationDisplay;
  12753. }(TimeDisplay);
  12754. /**
  12755. * The text that is added to the `DurationDisplay` for screen reader users.
  12756. *
  12757. * @type {string}
  12758. * @private
  12759. */
  12760. DurationDisplay.prototype.labelText_ = 'Duration';
  12761. /**
  12762. * The text that should display over the `DurationDisplay`s controls. Added to for localization.
  12763. *
  12764. * @type {string}
  12765. * @private
  12766. *
  12767. * @deprecated in v7; controlText_ is not used in non-active display Components
  12768. */
  12769. DurationDisplay.prototype.controlText_ = 'Duration';
  12770. Component.registerComponent('DurationDisplay', DurationDisplay);
  12771. /**
  12772. * The separator between the current time and duration.
  12773. * Can be hidden if it's not needed in the design.
  12774. *
  12775. * @extends Component
  12776. */
  12777. var TimeDivider =
  12778. /*#__PURE__*/
  12779. function (_Component) {
  12780. _inheritsLoose(TimeDivider, _Component);
  12781. function TimeDivider() {
  12782. return _Component.apply(this, arguments) || this;
  12783. }
  12784. var _proto = TimeDivider.prototype;
  12785. /**
  12786. * Create the component's DOM element
  12787. *
  12788. * @return {Element}
  12789. * The element that was created.
  12790. */
  12791. _proto.createEl = function createEl() {
  12792. return _Component.prototype.createEl.call(this, 'div', {
  12793. className: 'vjs-time-control vjs-time-divider',
  12794. innerHTML: '<div><span>/</span></div>'
  12795. }, {
  12796. // this element and its contents can be hidden from assistive techs since
  12797. // it is made extraneous by the announcement of the control text
  12798. // for the current time and duration displays
  12799. 'aria-hidden': true
  12800. });
  12801. };
  12802. return TimeDivider;
  12803. }(Component);
  12804. Component.registerComponent('TimeDivider', TimeDivider);
  12805. /**
  12806. * Displays the time left in the video
  12807. *
  12808. * @extends Component
  12809. */
  12810. var RemainingTimeDisplay =
  12811. /*#__PURE__*/
  12812. function (_TimeDisplay) {
  12813. _inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
  12814. /**
  12815. * Creates an instance of this class.
  12816. *
  12817. * @param {Player} player
  12818. * The `Player` that this class should be attached to.
  12819. *
  12820. * @param {Object} [options]
  12821. * The key/value store of player options.
  12822. */
  12823. function RemainingTimeDisplay(player, options) {
  12824. var _this;
  12825. _this = _TimeDisplay.call(this, player, options) || this;
  12826. _this.on(player, 'durationchange', _this.throttledUpdateContent);
  12827. _this.on(player, 'ended', _this.handleEnded);
  12828. return _this;
  12829. }
  12830. /**
  12831. * Builds the default DOM `className`.
  12832. *
  12833. * @return {string}
  12834. * The DOM `className` for this object.
  12835. */
  12836. var _proto = RemainingTimeDisplay.prototype;
  12837. _proto.buildCSSClass = function buildCSSClass() {
  12838. return 'vjs-remaining-time';
  12839. }
  12840. /**
  12841. * Create the `Component`'s DOM element with the "minus" characted prepend to the time
  12842. *
  12843. * @return {Element}
  12844. * The element that was created.
  12845. */
  12846. ;
  12847. _proto.createEl = function createEl$1() {
  12848. var el = _TimeDisplay.prototype.createEl.call(this);
  12849. el.insertBefore(createEl('span', {}, {
  12850. 'aria-hidden': true
  12851. }, '-'), this.contentEl_);
  12852. return el;
  12853. }
  12854. /**
  12855. * Update remaining time display.
  12856. *
  12857. * @param {EventTarget~Event} [event]
  12858. * The `timeupdate` or `durationchange` event that caused this to run.
  12859. *
  12860. * @listens Player#timeupdate
  12861. * @listens Player#durationchange
  12862. */
  12863. ;
  12864. _proto.updateContent = function updateContent(event) {
  12865. if (typeof this.player_.duration() !== 'number') {
  12866. return;
  12867. } // @deprecated We should only use remainingTimeDisplay
  12868. // as of video.js 7
  12869. if (this.player_.remainingTimeDisplay) {
  12870. this.updateFormattedTime_(this.player_.remainingTimeDisplay());
  12871. } else {
  12872. this.updateFormattedTime_(this.player_.remainingTime());
  12873. }
  12874. }
  12875. /**
  12876. * When the player fires ended there should be no time left. Sadly
  12877. * this is not always the case, lets make it seem like that is the case
  12878. * for users.
  12879. *
  12880. * @param {EventTarget~Event} [event]
  12881. * The `ended` event that caused this to run.
  12882. *
  12883. * @listens Player#ended
  12884. */
  12885. ;
  12886. _proto.handleEnded = function handleEnded(event) {
  12887. if (!this.player_.duration()) {
  12888. return;
  12889. }
  12890. this.updateFormattedTime_(0);
  12891. };
  12892. return RemainingTimeDisplay;
  12893. }(TimeDisplay);
  12894. /**
  12895. * The text that is added to the `RemainingTimeDisplay` for screen reader users.
  12896. *
  12897. * @type {string}
  12898. * @private
  12899. */
  12900. RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
  12901. /**
  12902. * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
  12903. *
  12904. * @type {string}
  12905. * @private
  12906. *
  12907. * @deprecated in v7; controlText_ is not used in non-active display Components
  12908. */
  12909. RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
  12910. Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
  12911. /**
  12912. * Displays the live indicator when duration is Infinity.
  12913. *
  12914. * @extends Component
  12915. */
  12916. var LiveDisplay =
  12917. /*#__PURE__*/
  12918. function (_Component) {
  12919. _inheritsLoose(LiveDisplay, _Component);
  12920. /**
  12921. * Creates an instance of this class.
  12922. *
  12923. * @param {Player} player
  12924. * The `Player` that this class should be attached to.
  12925. *
  12926. * @param {Object} [options]
  12927. * The key/value store of player options.
  12928. */
  12929. function LiveDisplay(player, options) {
  12930. var _this;
  12931. _this = _Component.call(this, player, options) || this;
  12932. _this.updateShowing();
  12933. _this.on(_this.player(), 'durationchange', _this.updateShowing);
  12934. return _this;
  12935. }
  12936. /**
  12937. * Create the `Component`'s DOM element
  12938. *
  12939. * @return {Element}
  12940. * The element that was created.
  12941. */
  12942. var _proto = LiveDisplay.prototype;
  12943. _proto.createEl = function createEl$1() {
  12944. var el = _Component.prototype.createEl.call(this, 'div', {
  12945. className: 'vjs-live-control vjs-control'
  12946. });
  12947. this.contentEl_ = createEl('div', {
  12948. className: 'vjs-live-display',
  12949. innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE')
  12950. }, {
  12951. 'aria-live': 'off'
  12952. });
  12953. el.appendChild(this.contentEl_);
  12954. return el;
  12955. };
  12956. _proto.dispose = function dispose() {
  12957. this.contentEl_ = null;
  12958. _Component.prototype.dispose.call(this);
  12959. }
  12960. /**
  12961. * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
  12962. * it accordingly
  12963. *
  12964. * @param {EventTarget~Event} [event]
  12965. * The {@link Player#durationchange} event that caused this function to run.
  12966. *
  12967. * @listens Player#durationchange
  12968. */
  12969. ;
  12970. _proto.updateShowing = function updateShowing(event) {
  12971. if (this.player().duration() === Infinity) {
  12972. this.show();
  12973. } else {
  12974. this.hide();
  12975. }
  12976. };
  12977. return LiveDisplay;
  12978. }(Component);
  12979. Component.registerComponent('LiveDisplay', LiveDisplay);
  12980. /**
  12981. * Displays the live indicator when duration is Infinity.
  12982. *
  12983. * @extends Component
  12984. */
  12985. var SeekToLive =
  12986. /*#__PURE__*/
  12987. function (_Button) {
  12988. _inheritsLoose(SeekToLive, _Button);
  12989. /**
  12990. * Creates an instance of this class.
  12991. *
  12992. * @param {Player} player
  12993. * The `Player` that this class should be attached to.
  12994. *
  12995. * @param {Object} [options]
  12996. * The key/value store of player options.
  12997. */
  12998. function SeekToLive(player, options) {
  12999. var _this;
  13000. _this = _Button.call(this, player, options) || this;
  13001. _this.updateLiveEdgeStatus();
  13002. if (_this.player_.liveTracker) {
  13003. _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus);
  13004. }
  13005. return _this;
  13006. }
  13007. /**
  13008. * Create the `Component`'s DOM element
  13009. *
  13010. * @return {Element}
  13011. * The element that was created.
  13012. */
  13013. var _proto = SeekToLive.prototype;
  13014. _proto.createEl = function createEl$1() {
  13015. var el = _Button.prototype.createEl.call(this, 'button', {
  13016. className: 'vjs-seek-to-live-control vjs-control'
  13017. });
  13018. this.textEl_ = createEl('span', {
  13019. className: 'vjs-seek-to-live-text',
  13020. innerHTML: this.localize('LIVE')
  13021. }, {
  13022. 'aria-hidden': 'true'
  13023. });
  13024. el.appendChild(this.textEl_);
  13025. return el;
  13026. }
  13027. /**
  13028. * Update the state of this button if we are at the live edge
  13029. * or not
  13030. */
  13031. ;
  13032. _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) {
  13033. // default to live edge
  13034. if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
  13035. this.setAttribute('aria-disabled', true);
  13036. this.addClass('vjs-at-live-edge');
  13037. this.controlText('Seek to live, currently playing live');
  13038. } else {
  13039. this.setAttribute('aria-disabled', false);
  13040. this.removeClass('vjs-at-live-edge');
  13041. this.controlText('Seek to live, currently behind live');
  13042. }
  13043. }
  13044. /**
  13045. * On click bring us as near to the live point as possible.
  13046. * This requires that we wait for the next `live-seekable-change`
  13047. * event which will happen every segment length seconds.
  13048. */
  13049. ;
  13050. _proto.handleClick = function handleClick() {
  13051. this.player_.liveTracker.seekToLiveEdge();
  13052. }
  13053. /**
  13054. * Dispose of the element and stop tracking
  13055. */
  13056. ;
  13057. _proto.dispose = function dispose() {
  13058. if (this.player_.liveTracker) {
  13059. this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus);
  13060. }
  13061. this.textEl_ = null;
  13062. _Button.prototype.dispose.call(this);
  13063. };
  13064. return SeekToLive;
  13065. }(Button);
  13066. SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
  13067. Component.registerComponent('SeekToLive', SeekToLive);
  13068. /**
  13069. * The base functionality for a slider. Can be vertical or horizontal.
  13070. * For instance the volume bar or the seek bar on a video is a slider.
  13071. *
  13072. * @extends Component
  13073. */
  13074. var Slider =
  13075. /*#__PURE__*/
  13076. function (_Component) {
  13077. _inheritsLoose(Slider, _Component);
  13078. /**
  13079. * Create an instance of this class
  13080. *
  13081. * @param {Player} player
  13082. * The `Player` that this class should be attached to.
  13083. *
  13084. * @param {Object} [options]
  13085. * The key/value store of player options.
  13086. */
  13087. function Slider(player, options) {
  13088. var _this;
  13089. _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for
  13090. _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
  13091. _this.vertical(!!_this.options_.vertical);
  13092. _this.enable();
  13093. return _this;
  13094. }
  13095. /**
  13096. * Are controls are currently enabled for this slider or not.
  13097. *
  13098. * @return {boolean}
  13099. * true if controls are enabled, false otherwise
  13100. */
  13101. var _proto = Slider.prototype;
  13102. _proto.enabled = function enabled() {
  13103. return this.enabled_;
  13104. }
  13105. /**
  13106. * Enable controls for this slider if they are disabled
  13107. */
  13108. ;
  13109. _proto.enable = function enable() {
  13110. if (this.enabled()) {
  13111. return;
  13112. }
  13113. this.on('mousedown', this.handleMouseDown);
  13114. this.on('touchstart', this.handleMouseDown);
  13115. this.on('keydown', this.handleKeyDown);
  13116. this.on('click', this.handleClick);
  13117. this.on(this.player_, 'controlsvisible', this.update);
  13118. if (this.playerEvent) {
  13119. this.on(this.player_, this.playerEvent, this.update);
  13120. }
  13121. this.removeClass('disabled');
  13122. this.setAttribute('tabindex', 0);
  13123. this.enabled_ = true;
  13124. }
  13125. /**
  13126. * Disable controls for this slider if they are enabled
  13127. */
  13128. ;
  13129. _proto.disable = function disable() {
  13130. if (!this.enabled()) {
  13131. return;
  13132. }
  13133. var doc = this.bar.el_.ownerDocument;
  13134. this.off('mousedown', this.handleMouseDown);
  13135. this.off('touchstart', this.handleMouseDown);
  13136. this.off('keydown', this.handleKeyDown);
  13137. this.off('click', this.handleClick);
  13138. this.off(this.player_, 'controlsvisible', this.update);
  13139. this.off(doc, 'mousemove', this.handleMouseMove);
  13140. this.off(doc, 'mouseup', this.handleMouseUp);
  13141. this.off(doc, 'touchmove', this.handleMouseMove);
  13142. this.off(doc, 'touchend', this.handleMouseUp);
  13143. this.removeAttribute('tabindex');
  13144. this.addClass('disabled');
  13145. if (this.playerEvent) {
  13146. this.off(this.player_, this.playerEvent, this.update);
  13147. }
  13148. this.enabled_ = false;
  13149. }
  13150. /**
  13151. * Create the `Slider`s DOM element.
  13152. *
  13153. * @param {string} type
  13154. * Type of element to create.
  13155. *
  13156. * @param {Object} [props={}]
  13157. * List of properties in Object form.
  13158. *
  13159. * @param {Object} [attributes={}]
  13160. * list of attributes in Object form.
  13161. *
  13162. * @return {Element}
  13163. * The element that gets created.
  13164. */
  13165. ;
  13166. _proto.createEl = function createEl(type, props, attributes) {
  13167. if (props === void 0) {
  13168. props = {};
  13169. }
  13170. if (attributes === void 0) {
  13171. attributes = {};
  13172. }
  13173. // Add the slider element class to all sub classes
  13174. props.className = props.className + ' vjs-slider';
  13175. props = assign({
  13176. tabIndex: 0
  13177. }, props);
  13178. attributes = assign({
  13179. 'role': 'slider',
  13180. 'aria-valuenow': 0,
  13181. 'aria-valuemin': 0,
  13182. 'aria-valuemax': 100,
  13183. 'tabIndex': 0
  13184. }, attributes);
  13185. return _Component.prototype.createEl.call(this, type, props, attributes);
  13186. }
  13187. /**
  13188. * Handle `mousedown` or `touchstart` events on the `Slider`.
  13189. *
  13190. * @param {EventTarget~Event} event
  13191. * `mousedown` or `touchstart` event that triggered this function
  13192. *
  13193. * @listens mousedown
  13194. * @listens touchstart
  13195. * @fires Slider#slideractive
  13196. */
  13197. ;
  13198. _proto.handleMouseDown = function handleMouseDown(event) {
  13199. var doc = this.bar.el_.ownerDocument;
  13200. if (event.type === 'mousedown') {
  13201. event.preventDefault();
  13202. } // Do not call preventDefault() on touchstart in Chrome
  13203. // to avoid console warnings. Use a 'touch-action: none' style
  13204. // instead to prevent unintented scrolling.
  13205. // https://developers.google.com/web/updates/2017/01/scrolling-intervention
  13206. if (event.type === 'touchstart' && !IS_CHROME) {
  13207. event.preventDefault();
  13208. }
  13209. blockTextSelection();
  13210. this.addClass('vjs-sliding');
  13211. /**
  13212. * Triggered when the slider is in an active state
  13213. *
  13214. * @event Slider#slideractive
  13215. * @type {EventTarget~Event}
  13216. */
  13217. this.trigger('slideractive');
  13218. this.on(doc, 'mousemove', this.handleMouseMove);
  13219. this.on(doc, 'mouseup', this.handleMouseUp);
  13220. this.on(doc, 'touchmove', this.handleMouseMove);
  13221. this.on(doc, 'touchend', this.handleMouseUp);
  13222. this.handleMouseMove(event);
  13223. }
  13224. /**
  13225. * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
  13226. * The `mousemove` and `touchmove` events will only only trigger this function during
  13227. * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
  13228. * {@link Slider#handleMouseUp}.
  13229. *
  13230. * @param {EventTarget~Event} event
  13231. * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
  13232. * this function
  13233. *
  13234. * @listens mousemove
  13235. * @listens touchmove
  13236. */
  13237. ;
  13238. _proto.handleMouseMove = function handleMouseMove(event) {}
  13239. /**
  13240. * Handle `mouseup` or `touchend` events on the `Slider`.
  13241. *
  13242. * @param {EventTarget~Event} event
  13243. * `mouseup` or `touchend` event that triggered this function.
  13244. *
  13245. * @listens touchend
  13246. * @listens mouseup
  13247. * @fires Slider#sliderinactive
  13248. */
  13249. ;
  13250. _proto.handleMouseUp = function handleMouseUp() {
  13251. var doc = this.bar.el_.ownerDocument;
  13252. unblockTextSelection();
  13253. this.removeClass('vjs-sliding');
  13254. /**
  13255. * Triggered when the slider is no longer in an active state.
  13256. *
  13257. * @event Slider#sliderinactive
  13258. * @type {EventTarget~Event}
  13259. */
  13260. this.trigger('sliderinactive');
  13261. this.off(doc, 'mousemove', this.handleMouseMove);
  13262. this.off(doc, 'mouseup', this.handleMouseUp);
  13263. this.off(doc, 'touchmove', this.handleMouseMove);
  13264. this.off(doc, 'touchend', this.handleMouseUp);
  13265. this.update();
  13266. }
  13267. /**
  13268. * Update the progress bar of the `Slider`.
  13269. *
  13270. * @return {number}
  13271. * The percentage of progress the progress bar represents as a
  13272. * number from 0 to 1.
  13273. */
  13274. ;
  13275. _proto.update = function update() {
  13276. // In VolumeBar init we have a setTimeout for update that pops and update
  13277. // to the end of the execution stack. The player is destroyed before then
  13278. // update will cause an error
  13279. if (!this.el_) {
  13280. return;
  13281. } // If scrubbing, we could use a cached value to make the handle keep up
  13282. // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
  13283. // some flash players are slow, so we might want to utilize this later.
  13284. // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  13285. var progress = this.getPercent();
  13286. var bar = this.bar; // If there's no bar...
  13287. if (!bar) {
  13288. return;
  13289. } // Protect against no duration and other division issues
  13290. if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
  13291. progress = 0;
  13292. } // Convert to a percentage for setting
  13293. var percentage = (progress * 100).toFixed(2) + '%';
  13294. var style = bar.el().style; // Set the new bar width or height
  13295. if (this.vertical()) {
  13296. style.height = percentage;
  13297. } else {
  13298. style.width = percentage;
  13299. }
  13300. return progress;
  13301. }
  13302. /**
  13303. * Calculate distance for slider
  13304. *
  13305. * @param {EventTarget~Event} event
  13306. * The event that caused this function to run.
  13307. *
  13308. * @return {number}
  13309. * The current position of the Slider.
  13310. * - position.x for vertical `Slider`s
  13311. * - position.y for horizontal `Slider`s
  13312. */
  13313. ;
  13314. _proto.calculateDistance = function calculateDistance(event) {
  13315. var position = getPointerPosition(this.el_, event);
  13316. if (this.vertical()) {
  13317. return position.y;
  13318. }
  13319. return position.x;
  13320. }
  13321. /**
  13322. * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
  13323. * arrow keys. This function will only be called when the slider has focus. See
  13324. * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
  13325. *
  13326. * @param {EventTarget~Event} event
  13327. * the `keydown` event that caused this function to run.
  13328. *
  13329. * @listens keydown
  13330. */
  13331. ;
  13332. _proto.handleKeyDown = function handleKeyDown(event) {
  13333. // Left and Down Arrows
  13334. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  13335. event.preventDefault();
  13336. event.stopPropagation();
  13337. this.stepBack(); // Up and Right Arrows
  13338. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  13339. event.preventDefault();
  13340. event.stopPropagation();
  13341. this.stepForward();
  13342. } else {
  13343. // Pass keydown handling up for unsupported keys
  13344. _Component.prototype.handleKeyDown.call(this, event);
  13345. }
  13346. }
  13347. /**
  13348. * Listener for click events on slider, used to prevent clicks
  13349. * from bubbling up to parent elements like button menus.
  13350. *
  13351. * @param {Object} event
  13352. * Event that caused this object to run
  13353. */
  13354. ;
  13355. _proto.handleClick = function handleClick(event) {
  13356. event.stopPropagation();
  13357. event.preventDefault();
  13358. }
  13359. /**
  13360. * Get/set if slider is horizontal for vertical
  13361. *
  13362. * @param {boolean} [bool]
  13363. * - true if slider is vertical,
  13364. * - false is horizontal
  13365. *
  13366. * @return {boolean}
  13367. * - true if slider is vertical, and getting
  13368. * - false if the slider is horizontal, and getting
  13369. */
  13370. ;
  13371. _proto.vertical = function vertical(bool) {
  13372. if (bool === undefined) {
  13373. return this.vertical_ || false;
  13374. }
  13375. this.vertical_ = !!bool;
  13376. if (this.vertical_) {
  13377. this.addClass('vjs-slider-vertical');
  13378. } else {
  13379. this.addClass('vjs-slider-horizontal');
  13380. }
  13381. };
  13382. return Slider;
  13383. }(Component);
  13384. Component.registerComponent('Slider', Slider);
  13385. /**
  13386. * Shows loading progress
  13387. *
  13388. * @extends Component
  13389. */
  13390. var LoadProgressBar =
  13391. /*#__PURE__*/
  13392. function (_Component) {
  13393. _inheritsLoose(LoadProgressBar, _Component);
  13394. /**
  13395. * Creates an instance of this class.
  13396. *
  13397. * @param {Player} player
  13398. * The `Player` that this class should be attached to.
  13399. *
  13400. * @param {Object} [options]
  13401. * The key/value store of player options.
  13402. */
  13403. function LoadProgressBar(player, options) {
  13404. var _this;
  13405. _this = _Component.call(this, player, options) || this;
  13406. _this.partEls_ = [];
  13407. _this.on(player, 'progress', _this.update);
  13408. return _this;
  13409. }
  13410. /**
  13411. * Create the `Component`'s DOM element
  13412. *
  13413. * @return {Element}
  13414. * The element that was created.
  13415. */
  13416. var _proto = LoadProgressBar.prototype;
  13417. _proto.createEl = function createEl() {
  13418. return _Component.prototype.createEl.call(this, 'div', {
  13419. className: 'vjs-load-progress',
  13420. innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>"
  13421. });
  13422. };
  13423. _proto.dispose = function dispose() {
  13424. this.partEls_ = null;
  13425. _Component.prototype.dispose.call(this);
  13426. }
  13427. /**
  13428. * Update progress bar
  13429. *
  13430. * @param {EventTarget~Event} [event]
  13431. * The `progress` event that caused this function to run.
  13432. *
  13433. * @listens Player#progress
  13434. */
  13435. ;
  13436. _proto.update = function update(event) {
  13437. var liveTracker = this.player_.liveTracker;
  13438. var buffered = this.player_.buffered();
  13439. var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
  13440. var bufferedEnd = this.player_.bufferedEnd();
  13441. var children = this.partEls_;
  13442. var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end
  13443. var percentify = function percentify(time, end, rounded) {
  13444. // no NaN
  13445. var percent = time / end || 0;
  13446. percent = (percent >= 1 ? 1 : percent) * 100;
  13447. if (rounded) {
  13448. percent = percent.toFixed(2);
  13449. }
  13450. return percent + '%';
  13451. }; // update the width of the progress bar
  13452. this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text
  13453. textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges
  13454. for (var i = 0; i < buffered.length; i++) {
  13455. var start = buffered.start(i);
  13456. var end = buffered.end(i);
  13457. var part = children[i];
  13458. if (!part) {
  13459. part = this.el_.appendChild(createEl());
  13460. children[i] = part;
  13461. } // set the percent based on the width of the progress bar (bufferedEnd)
  13462. part.style.left = percentify(start, bufferedEnd);
  13463. part.style.width = percentify(end - start, bufferedEnd);
  13464. } // remove unused buffered range elements
  13465. for (var _i = children.length; _i > buffered.length; _i--) {
  13466. this.el_.removeChild(children[_i - 1]);
  13467. }
  13468. children.length = buffered.length;
  13469. };
  13470. return LoadProgressBar;
  13471. }(Component);
  13472. Component.registerComponent('LoadProgressBar', LoadProgressBar);
  13473. /**
  13474. * Time tooltips display a time above the progress bar.
  13475. *
  13476. * @extends Component
  13477. */
  13478. var TimeTooltip =
  13479. /*#__PURE__*/
  13480. function (_Component) {
  13481. _inheritsLoose(TimeTooltip, _Component);
  13482. function TimeTooltip() {
  13483. return _Component.apply(this, arguments) || this;
  13484. }
  13485. var _proto = TimeTooltip.prototype;
  13486. /**
  13487. * Create the time tooltip DOM element
  13488. *
  13489. * @return {Element}
  13490. * The element that was created.
  13491. */
  13492. _proto.createEl = function createEl() {
  13493. return _Component.prototype.createEl.call(this, 'div', {
  13494. className: 'vjs-time-tooltip'
  13495. }, {
  13496. 'aria-hidden': 'true'
  13497. });
  13498. }
  13499. /**
  13500. * Updates the position of the time tooltip relative to the `SeekBar`.
  13501. *
  13502. * @param {Object} seekBarRect
  13503. * The `ClientRect` for the {@link SeekBar} element.
  13504. *
  13505. * @param {number} seekBarPoint
  13506. * A number from 0 to 1, representing a horizontal reference point
  13507. * from the left edge of the {@link SeekBar}
  13508. */
  13509. ;
  13510. _proto.update = function update(seekBarRect, seekBarPoint, content) {
  13511. var tooltipRect = getBoundingClientRect(this.el_);
  13512. var playerRect = getBoundingClientRect(this.player_.el());
  13513. var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
  13514. // for example, if the player isn't in the DOM for testing
  13515. if (!playerRect || !tooltipRect) {
  13516. return;
  13517. } // This is the space left of the `seekBarPoint` available within the bounds
  13518. // of the player. We calculate any gap between the left edge of the player
  13519. // and the left edge of the `SeekBar` and add the number of pixels in the
  13520. // `SeekBar` before hitting the `seekBarPoint`
  13521. var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
  13522. // of the player. We calculate the number of pixels from the `seekBarPoint`
  13523. // to the right edge of the `SeekBar` and add to that any gap between the
  13524. // right edge of the `SeekBar` and the player.
  13525. var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
  13526. // further to the right to center it over the `seekBarPoint`.
  13527. var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
  13528. // the results of the space calculations above.
  13529. if (spaceLeftOfPoint < pullTooltipBy) {
  13530. pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  13531. } else if (spaceRightOfPoint < pullTooltipBy) {
  13532. pullTooltipBy = spaceRightOfPoint;
  13533. } // Due to the imprecision of decimal/ratio based calculations and varying
  13534. // rounding behaviors, there are cases where the spacing adjustment is off
  13535. // by a pixel or two. This adds insurance to these calculations.
  13536. if (pullTooltipBy < 0) {
  13537. pullTooltipBy = 0;
  13538. } else if (pullTooltipBy > tooltipRect.width) {
  13539. pullTooltipBy = tooltipRect.width;
  13540. }
  13541. this.el_.style.right = "-" + pullTooltipBy + "px";
  13542. this.write(content);
  13543. }
  13544. /**
  13545. * Write the time to the tooltip DOM element.
  13546. *
  13547. * @param {String} content
  13548. * The formatted time for the tooltip.
  13549. */
  13550. ;
  13551. _proto.write = function write(content) {
  13552. textContent(this.el_, content);
  13553. }
  13554. /**
  13555. * Updates the position of the time tooltip relative to the `SeekBar`.
  13556. *
  13557. * @param {Object} seekBarRect
  13558. * The `ClientRect` for the {@link SeekBar} element.
  13559. *
  13560. * @param {number} seekBarPoint
  13561. * A number from 0 to 1, representing a horizontal reference point
  13562. * from the left edge of the {@link SeekBar}
  13563. *
  13564. * @param {number} time
  13565. * The time to update the tooltip to, not used during live playback
  13566. *
  13567. * @param {Function} cb
  13568. * A function that will be called during the request animation frame
  13569. * for tooltips that need to do additional animations from the default
  13570. */
  13571. ;
  13572. _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
  13573. var _this = this;
  13574. // If there is an existing rAF ID, cancel it so we don't over-queue.
  13575. if (this.rafId_) {
  13576. this.cancelAnimationFrame(this.rafId_);
  13577. }
  13578. this.rafId_ = this.requestAnimationFrame(function () {
  13579. var content;
  13580. var duration = _this.player_.duration();
  13581. if (_this.player_.liveTracker && _this.player_.liveTracker.isLive()) {
  13582. var liveWindow = _this.player_.liveTracker.liveWindow();
  13583. var secondsBehind = liveWindow - seekBarPoint * liveWindow;
  13584. content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
  13585. } else {
  13586. content = formatTime(time, duration);
  13587. }
  13588. _this.update(seekBarRect, seekBarPoint, content);
  13589. if (cb) {
  13590. cb();
  13591. }
  13592. });
  13593. };
  13594. return TimeTooltip;
  13595. }(Component);
  13596. Component.registerComponent('TimeTooltip', TimeTooltip);
  13597. /**
  13598. * Used by {@link SeekBar} to display media playback progress as part of the
  13599. * {@link ProgressControl}.
  13600. *
  13601. * @extends Component
  13602. */
  13603. var PlayProgressBar =
  13604. /*#__PURE__*/
  13605. function (_Component) {
  13606. _inheritsLoose(PlayProgressBar, _Component);
  13607. function PlayProgressBar() {
  13608. return _Component.apply(this, arguments) || this;
  13609. }
  13610. var _proto = PlayProgressBar.prototype;
  13611. /**
  13612. * Create the the DOM element for this class.
  13613. *
  13614. * @return {Element}
  13615. * The element that was created.
  13616. */
  13617. _proto.createEl = function createEl() {
  13618. return _Component.prototype.createEl.call(this, 'div', {
  13619. className: 'vjs-play-progress vjs-slider-bar'
  13620. }, {
  13621. 'aria-hidden': 'true'
  13622. });
  13623. }
  13624. /**
  13625. * Enqueues updates to its own DOM as well as the DOM of its
  13626. * {@link TimeTooltip} child.
  13627. *
  13628. * @param {Object} seekBarRect
  13629. * The `ClientRect` for the {@link SeekBar} element.
  13630. *
  13631. * @param {number} seekBarPoint
  13632. * A number from 0 to 1, representing a horizontal reference point
  13633. * from the left edge of the {@link SeekBar}
  13634. */
  13635. ;
  13636. _proto.update = function update(seekBarRect, seekBarPoint) {
  13637. var timeTooltip = this.getChild('timeTooltip');
  13638. if (!timeTooltip) {
  13639. return;
  13640. }
  13641. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  13642. timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
  13643. };
  13644. return PlayProgressBar;
  13645. }(Component);
  13646. /**
  13647. * Default options for {@link PlayProgressBar}.
  13648. *
  13649. * @type {Object}
  13650. * @private
  13651. */
  13652. PlayProgressBar.prototype.options_ = {
  13653. children: []
  13654. }; // Time tooltips should not be added to a player on mobile devices
  13655. if (!IS_IOS && !IS_ANDROID) {
  13656. PlayProgressBar.prototype.options_.children.push('timeTooltip');
  13657. }
  13658. Component.registerComponent('PlayProgressBar', PlayProgressBar);
  13659. /**
  13660. * The {@link MouseTimeDisplay} component tracks mouse movement over the
  13661. * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
  13662. * indicating the time which is represented by a given point in the
  13663. * {@link ProgressControl}.
  13664. *
  13665. * @extends Component
  13666. */
  13667. var MouseTimeDisplay =
  13668. /*#__PURE__*/
  13669. function (_Component) {
  13670. _inheritsLoose(MouseTimeDisplay, _Component);
  13671. /**
  13672. * Creates an instance of this class.
  13673. *
  13674. * @param {Player} player
  13675. * The {@link Player} that this class should be attached to.
  13676. *
  13677. * @param {Object} [options]
  13678. * The key/value store of player options.
  13679. */
  13680. function MouseTimeDisplay(player, options) {
  13681. var _this;
  13682. _this = _Component.call(this, player, options) || this;
  13683. _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), 25);
  13684. return _this;
  13685. }
  13686. /**
  13687. * Create the DOM element for this class.
  13688. *
  13689. * @return {Element}
  13690. * The element that was created.
  13691. */
  13692. var _proto = MouseTimeDisplay.prototype;
  13693. _proto.createEl = function createEl() {
  13694. return _Component.prototype.createEl.call(this, 'div', {
  13695. className: 'vjs-mouse-display'
  13696. });
  13697. }
  13698. /**
  13699. * Enqueues updates to its own DOM as well as the DOM of its
  13700. * {@link TimeTooltip} child.
  13701. *
  13702. * @param {Object} seekBarRect
  13703. * The `ClientRect` for the {@link SeekBar} element.
  13704. *
  13705. * @param {number} seekBarPoint
  13706. * A number from 0 to 1, representing a horizontal reference point
  13707. * from the left edge of the {@link SeekBar}
  13708. */
  13709. ;
  13710. _proto.update = function update(seekBarRect, seekBarPoint) {
  13711. var _this2 = this;
  13712. var time = seekBarPoint * this.player_.duration();
  13713. this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
  13714. _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
  13715. });
  13716. };
  13717. return MouseTimeDisplay;
  13718. }(Component);
  13719. /**
  13720. * Default options for `MouseTimeDisplay`
  13721. *
  13722. * @type {Object}
  13723. * @private
  13724. */
  13725. MouseTimeDisplay.prototype.options_ = {
  13726. children: ['timeTooltip']
  13727. };
  13728. Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
  13729. var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
  13730. var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses.
  13731. var UPDATE_REFRESH_INTERVAL = 30;
  13732. /**
  13733. * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
  13734. * as its `bar`.
  13735. *
  13736. * @extends Slider
  13737. */
  13738. var SeekBar =
  13739. /*#__PURE__*/
  13740. function (_Slider) {
  13741. _inheritsLoose(SeekBar, _Slider);
  13742. /**
  13743. * Creates an instance of this class.
  13744. *
  13745. * @param {Player} player
  13746. * The `Player` that this class should be attached to.
  13747. *
  13748. * @param {Object} [options]
  13749. * The key/value store of player options.
  13750. */
  13751. function SeekBar(player, options) {
  13752. var _this;
  13753. _this = _Slider.call(this, player, options) || this;
  13754. _this.setEventHandlers_();
  13755. return _this;
  13756. }
  13757. /**
  13758. * Sets the event handlers
  13759. *
  13760. * @private
  13761. */
  13762. var _proto = SeekBar.prototype;
  13763. _proto.setEventHandlers_ = function setEventHandlers_() {
  13764. this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
  13765. this.on(this.player_, 'timeupdate', this.update);
  13766. this.on(this.player_, 'ended', this.handleEnded);
  13767. this.on(this.player_, 'durationchange', this.update);
  13768. if (this.player_.liveTracker) {
  13769. this.on(this.player_.liveTracker, 'liveedgechange', this.update);
  13770. } // when playing, let's ensure we smoothly update the play progress bar
  13771. // via an interval
  13772. this.updateInterval = null;
  13773. this.on(this.player_, ['playing'], this.enableInterval_);
  13774. this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
  13775. // also, this causes the CPU to spike and eventually crash the page on IE11.
  13776. if ('hidden' in document && 'visibilityState' in document) {
  13777. this.on(document, 'visibilitychange', this.toggleVisibility_);
  13778. }
  13779. };
  13780. _proto.toggleVisibility_ = function toggleVisibility_(e) {
  13781. if (document.hidden) {
  13782. this.disableInterval_(e);
  13783. } else {
  13784. this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
  13785. this.requestAnimationFrame(this.update);
  13786. }
  13787. };
  13788. _proto.enableInterval_ = function enableInterval_() {
  13789. var _this2 = this;
  13790. this.clearInterval(this.updateInterval);
  13791. this.updateInterval = this.setInterval(function () {
  13792. _this2.requestAnimationFrame(_this2.update);
  13793. }, UPDATE_REFRESH_INTERVAL);
  13794. };
  13795. _proto.disableInterval_ = function disableInterval_(e) {
  13796. if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') {
  13797. return;
  13798. }
  13799. this.clearInterval(this.updateInterval);
  13800. }
  13801. /**
  13802. * Create the `Component`'s DOM element
  13803. *
  13804. * @return {Element}
  13805. * The element that was created.
  13806. */
  13807. ;
  13808. _proto.createEl = function createEl() {
  13809. return _Slider.prototype.createEl.call(this, 'div', {
  13810. className: 'vjs-progress-holder'
  13811. }, {
  13812. 'aria-label': this.localize('Progress Bar')
  13813. });
  13814. }
  13815. /**
  13816. * This function updates the play progress bar and accessibility
  13817. * attributes to whatever is passed in.
  13818. *
  13819. * @param {number} currentTime
  13820. * The currentTime value that should be used for accessibility
  13821. *
  13822. * @param {number} percent
  13823. * The percentage as a decimal that the bar should be filled from 0-1.
  13824. *
  13825. * @private
  13826. */
  13827. ;
  13828. _proto.update_ = function update_(currentTime, percent) {
  13829. var liveTracker = this.player_.liveTracker;
  13830. var duration = this.player_.duration();
  13831. if (liveTracker && liveTracker.isLive()) {
  13832. duration = this.player_.liveTracker.liveCurrentTime();
  13833. } // machine readable value of progress bar (percentage complete)
  13834. this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete)
  13835. 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`.
  13836. if (this.bar) {
  13837. this.bar.update(getBoundingClientRect(this.el_), percent);
  13838. }
  13839. }
  13840. /**
  13841. * Update the seek bar's UI.
  13842. *
  13843. * @param {EventTarget~Event} [event]
  13844. * The `timeupdate` or `ended` event that caused this to run.
  13845. *
  13846. * @listens Player#timeupdate
  13847. *
  13848. * @return {number}
  13849. * The current percent at a number from 0-1
  13850. */
  13851. ;
  13852. _proto.update = function update(event) {
  13853. // if the offsetParent is null, then this element is hidden, in which case
  13854. // we don't need to update it.
  13855. if (this.el().offsetParent === null) {
  13856. return;
  13857. }
  13858. var percent = _Slider.prototype.update.call(this);
  13859. this.update_(this.getCurrentTime_(), percent);
  13860. return percent;
  13861. }
  13862. /**
  13863. * Get the value of current time but allows for smooth scrubbing,
  13864. * when player can't keep up.
  13865. *
  13866. * @return {number}
  13867. * The current time value to display
  13868. *
  13869. * @private
  13870. */
  13871. ;
  13872. _proto.getCurrentTime_ = function getCurrentTime_() {
  13873. return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  13874. }
  13875. /**
  13876. * We want the seek bar to be full on ended
  13877. * no matter what the actual internal values are. so we force it.
  13878. *
  13879. * @param {EventTarget~Event} [event]
  13880. * The `timeupdate` or `ended` event that caused this to run.
  13881. *
  13882. * @listens Player#ended
  13883. */
  13884. ;
  13885. _proto.handleEnded = function handleEnded(event) {
  13886. this.update_(this.player_.duration(), 1);
  13887. }
  13888. /**
  13889. * Get the percentage of media played so far.
  13890. *
  13891. * @return {number}
  13892. * The percentage of media played so far (0 to 1).
  13893. */
  13894. ;
  13895. _proto.getPercent = function getPercent() {
  13896. var currentTime = this.getCurrentTime_();
  13897. var percent;
  13898. var liveTracker = this.player_.liveTracker;
  13899. if (liveTracker && liveTracker.isLive()) {
  13900. percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
  13901. if (liveTracker.atLiveEdge()) {
  13902. percent = 1;
  13903. }
  13904. } else {
  13905. percent = currentTime / this.player_.duration();
  13906. }
  13907. return percent >= 1 ? 1 : percent || 0;
  13908. }
  13909. /**
  13910. * Handle mouse down on seek bar
  13911. *
  13912. * @param {EventTarget~Event} event
  13913. * The `mousedown` event that caused this to run.
  13914. *
  13915. * @listens mousedown
  13916. */
  13917. ;
  13918. _proto.handleMouseDown = function handleMouseDown(event) {
  13919. if (!isSingleLeftClick(event)) {
  13920. return;
  13921. } // Stop event propagation to prevent double fire in progress-control.js
  13922. event.stopPropagation();
  13923. this.player_.scrubbing(true);
  13924. this.videoWasPlaying = !this.player_.paused();
  13925. this.player_.pause();
  13926. _Slider.prototype.handleMouseDown.call(this, event);
  13927. }
  13928. /**
  13929. * Handle mouse move on seek bar
  13930. *
  13931. * @param {EventTarget~Event} event
  13932. * The `mousemove` event that caused this to run.
  13933. *
  13934. * @listens mousemove
  13935. */
  13936. ;
  13937. _proto.handleMouseMove = function handleMouseMove(event) {
  13938. if (!isSingleLeftClick(event)) {
  13939. return;
  13940. }
  13941. var newTime;
  13942. var distance = this.calculateDistance(event);
  13943. var liveTracker = this.player_.liveTracker;
  13944. if (!liveTracker || !liveTracker.isLive()) {
  13945. newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
  13946. if (newTime === this.player_.duration()) {
  13947. newTime = newTime - 0.1;
  13948. }
  13949. } else {
  13950. var seekableStart = liveTracker.seekableStart();
  13951. var seekableEnd = liveTracker.liveCurrentTime();
  13952. newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
  13953. if (newTime >= seekableEnd) {
  13954. newTime = seekableEnd;
  13955. } // Compensate for precision differences so that currentTime is not less
  13956. // than seekable start
  13957. if (newTime <= seekableStart) {
  13958. newTime = seekableStart + 0.1;
  13959. } // On android seekableEnd can be Infinity sometimes,
  13960. // this will cause newTime to be Infinity, which is
  13961. // not a valid currentTime.
  13962. if (newTime === Infinity) {
  13963. return;
  13964. }
  13965. } // Set new time (tell player to seek to new time)
  13966. this.player_.currentTime(newTime);
  13967. };
  13968. _proto.enable = function enable() {
  13969. _Slider.prototype.enable.call(this);
  13970. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  13971. if (!mouseTimeDisplay) {
  13972. return;
  13973. }
  13974. mouseTimeDisplay.show();
  13975. };
  13976. _proto.disable = function disable() {
  13977. _Slider.prototype.disable.call(this);
  13978. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  13979. if (!mouseTimeDisplay) {
  13980. return;
  13981. }
  13982. mouseTimeDisplay.hide();
  13983. }
  13984. /**
  13985. * Handle mouse up on seek bar
  13986. *
  13987. * @param {EventTarget~Event} event
  13988. * The `mouseup` event that caused this to run.
  13989. *
  13990. * @listens mouseup
  13991. */
  13992. ;
  13993. _proto.handleMouseUp = function handleMouseUp(event) {
  13994. _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
  13995. if (event) {
  13996. event.stopPropagation();
  13997. }
  13998. this.player_.scrubbing(false);
  13999. /**
  14000. * Trigger timeupdate because we're done seeking and the time has changed.
  14001. * This is particularly useful for if the player is paused to time the time displays.
  14002. *
  14003. * @event Tech#timeupdate
  14004. * @type {EventTarget~Event}
  14005. */
  14006. this.player_.trigger({
  14007. type: 'timeupdate',
  14008. target: this,
  14009. manuallyTriggered: true
  14010. });
  14011. if (this.videoWasPlaying) {
  14012. silencePromise(this.player_.play());
  14013. }
  14014. }
  14015. /**
  14016. * Move more quickly fast forward for keyboard-only users
  14017. */
  14018. ;
  14019. _proto.stepForward = function stepForward() {
  14020. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
  14021. }
  14022. /**
  14023. * Move more quickly rewind for keyboard-only users
  14024. */
  14025. ;
  14026. _proto.stepBack = function stepBack() {
  14027. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
  14028. }
  14029. /**
  14030. * Toggles the playback state of the player
  14031. * This gets called when enter or space is used on the seekbar
  14032. *
  14033. * @param {EventTarget~Event} event
  14034. * The `keydown` event that caused this function to be called
  14035. *
  14036. */
  14037. ;
  14038. _proto.handleAction = function handleAction(event) {
  14039. if (this.player_.paused()) {
  14040. this.player_.play();
  14041. } else {
  14042. this.player_.pause();
  14043. }
  14044. }
  14045. /**
  14046. * Called when this SeekBar has focus and a key gets pressed down.
  14047. * Supports the following keys:
  14048. *
  14049. * Space or Enter key fire a click event
  14050. * Home key moves to start of the timeline
  14051. * End key moves to end of the timeline
  14052. * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
  14053. * PageDown key moves back a larger step than ArrowDown
  14054. * PageUp key moves forward a large step
  14055. *
  14056. * @param {EventTarget~Event} event
  14057. * The `keydown` event that caused this function to be called.
  14058. *
  14059. * @listens keydown
  14060. */
  14061. ;
  14062. _proto.handleKeyDown = function handleKeyDown(event) {
  14063. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  14064. event.preventDefault();
  14065. event.stopPropagation();
  14066. this.handleAction(event);
  14067. } else if (keycode.isEventKey(event, 'Home')) {
  14068. event.preventDefault();
  14069. event.stopPropagation();
  14070. this.player_.currentTime(0);
  14071. } else if (keycode.isEventKey(event, 'End')) {
  14072. event.preventDefault();
  14073. event.stopPropagation();
  14074. this.player_.currentTime(this.player_.duration());
  14075. } else if (/^[0-9]$/.test(keycode(event))) {
  14076. event.preventDefault();
  14077. event.stopPropagation();
  14078. var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
  14079. this.player_.currentTime(this.player_.duration() * gotoFraction);
  14080. } else if (keycode.isEventKey(event, 'PgDn')) {
  14081. event.preventDefault();
  14082. event.stopPropagation();
  14083. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  14084. } else if (keycode.isEventKey(event, 'PgUp')) {
  14085. event.preventDefault();
  14086. event.stopPropagation();
  14087. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  14088. } else {
  14089. // Pass keydown handling up for unsupported keys
  14090. _Slider.prototype.handleKeyDown.call(this, event);
  14091. }
  14092. };
  14093. return SeekBar;
  14094. }(Slider);
  14095. /**
  14096. * Default options for the `SeekBar`
  14097. *
  14098. * @type {Object}
  14099. * @private
  14100. */
  14101. SeekBar.prototype.options_ = {
  14102. children: ['loadProgressBar', 'playProgressBar'],
  14103. barName: 'playProgressBar'
  14104. }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
  14105. if (!IS_IOS && !IS_ANDROID) {
  14106. SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
  14107. }
  14108. Component.registerComponent('SeekBar', SeekBar);
  14109. /**
  14110. * The Progress Control component contains the seek bar, load progress,
  14111. * and play progress.
  14112. *
  14113. * @extends Component
  14114. */
  14115. var ProgressControl =
  14116. /*#__PURE__*/
  14117. function (_Component) {
  14118. _inheritsLoose(ProgressControl, _Component);
  14119. /**
  14120. * Creates an instance of this class.
  14121. *
  14122. * @param {Player} player
  14123. * The `Player` that this class should be attached to.
  14124. *
  14125. * @param {Object} [options]
  14126. * The key/value store of player options.
  14127. */
  14128. function ProgressControl(player, options) {
  14129. var _this;
  14130. _this = _Component.call(this, player, options) || this;
  14131. _this.handleMouseMove = throttle(bind(_assertThisInitialized(_this), _this.handleMouseMove), 25);
  14132. _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_this), _this.handleMouseSeek), 25);
  14133. _this.enable();
  14134. return _this;
  14135. }
  14136. /**
  14137. * Create the `Component`'s DOM element
  14138. *
  14139. * @return {Element}
  14140. * The element that was created.
  14141. */
  14142. var _proto = ProgressControl.prototype;
  14143. _proto.createEl = function createEl() {
  14144. return _Component.prototype.createEl.call(this, 'div', {
  14145. className: 'vjs-progress-control vjs-control'
  14146. });
  14147. }
  14148. /**
  14149. * When the mouse moves over the `ProgressControl`, the pointer position
  14150. * gets passed down to the `MouseTimeDisplay` component.
  14151. *
  14152. * @param {EventTarget~Event} event
  14153. * The `mousemove` event that caused this function to run.
  14154. *
  14155. * @listen mousemove
  14156. */
  14157. ;
  14158. _proto.handleMouseMove = function handleMouseMove(event) {
  14159. var seekBar = this.getChild('seekBar');
  14160. if (seekBar) {
  14161. var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
  14162. var seekBarEl = seekBar.el();
  14163. var seekBarRect = getBoundingClientRect(seekBarEl);
  14164. var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
  14165. // that it's possible to trigger this behavior outside the boundaries of
  14166. // the `SeekBar`. This ensures we stay within it at all times.
  14167. if (seekBarPoint > 1) {
  14168. seekBarPoint = 1;
  14169. } else if (seekBarPoint < 0) {
  14170. seekBarPoint = 0;
  14171. }
  14172. if (mouseTimeDisplay) {
  14173. mouseTimeDisplay.update(seekBarRect, seekBarPoint);
  14174. }
  14175. }
  14176. }
  14177. /**
  14178. * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
  14179. *
  14180. * @method ProgressControl#throttledHandleMouseSeek
  14181. * @param {EventTarget~Event} event
  14182. * The `mousemove` event that caused this function to run.
  14183. *
  14184. * @listen mousemove
  14185. * @listen touchmove
  14186. */
  14187. /**
  14188. * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
  14189. *
  14190. * @param {EventTarget~Event} event
  14191. * `mousedown` or `touchstart` event that triggered this function
  14192. *
  14193. * @listens mousemove
  14194. * @listens touchmove
  14195. */
  14196. ;
  14197. _proto.handleMouseSeek = function handleMouseSeek(event) {
  14198. var seekBar = this.getChild('seekBar');
  14199. if (seekBar) {
  14200. seekBar.handleMouseMove(event);
  14201. }
  14202. }
  14203. /**
  14204. * Are controls are currently enabled for this progress control.
  14205. *
  14206. * @return {boolean}
  14207. * true if controls are enabled, false otherwise
  14208. */
  14209. ;
  14210. _proto.enabled = function enabled() {
  14211. return this.enabled_;
  14212. }
  14213. /**
  14214. * Disable all controls on the progress control and its children
  14215. */
  14216. ;
  14217. _proto.disable = function disable() {
  14218. this.children().forEach(function (child) {
  14219. return child.disable && child.disable();
  14220. });
  14221. if (!this.enabled()) {
  14222. return;
  14223. }
  14224. this.off(['mousedown', 'touchstart'], this.handleMouseDown);
  14225. this.off(this.el_, 'mousemove', this.handleMouseMove);
  14226. this.handleMouseUp();
  14227. this.addClass('disabled');
  14228. this.enabled_ = false;
  14229. }
  14230. /**
  14231. * Enable all controls on the progress control and its children
  14232. */
  14233. ;
  14234. _proto.enable = function enable() {
  14235. this.children().forEach(function (child) {
  14236. return child.enable && child.enable();
  14237. });
  14238. if (this.enabled()) {
  14239. return;
  14240. }
  14241. this.on(['mousedown', 'touchstart'], this.handleMouseDown);
  14242. this.on(this.el_, 'mousemove', this.handleMouseMove);
  14243. this.removeClass('disabled');
  14244. this.enabled_ = true;
  14245. }
  14246. /**
  14247. * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
  14248. *
  14249. * @param {EventTarget~Event} event
  14250. * `mousedown` or `touchstart` event that triggered this function
  14251. *
  14252. * @listens mousedown
  14253. * @listens touchstart
  14254. */
  14255. ;
  14256. _proto.handleMouseDown = function handleMouseDown(event) {
  14257. var doc = this.el_.ownerDocument;
  14258. var seekBar = this.getChild('seekBar');
  14259. if (seekBar) {
  14260. seekBar.handleMouseDown(event);
  14261. }
  14262. this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
  14263. this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
  14264. this.on(doc, 'mouseup', this.handleMouseUp);
  14265. this.on(doc, 'touchend', this.handleMouseUp);
  14266. }
  14267. /**
  14268. * Handle `mouseup` or `touchend` events on the `ProgressControl`.
  14269. *
  14270. * @param {EventTarget~Event} event
  14271. * `mouseup` or `touchend` event that triggered this function.
  14272. *
  14273. * @listens touchend
  14274. * @listens mouseup
  14275. */
  14276. ;
  14277. _proto.handleMouseUp = function handleMouseUp(event) {
  14278. var doc = this.el_.ownerDocument;
  14279. var seekBar = this.getChild('seekBar');
  14280. if (seekBar) {
  14281. seekBar.handleMouseUp(event);
  14282. }
  14283. this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
  14284. this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
  14285. this.off(doc, 'mouseup', this.handleMouseUp);
  14286. this.off(doc, 'touchend', this.handleMouseUp);
  14287. };
  14288. return ProgressControl;
  14289. }(Component);
  14290. /**
  14291. * Default options for `ProgressControl`
  14292. *
  14293. * @type {Object}
  14294. * @private
  14295. */
  14296. ProgressControl.prototype.options_ = {
  14297. children: ['seekBar']
  14298. };
  14299. Component.registerComponent('ProgressControl', ProgressControl);
  14300. /**
  14301. * Toggle Picture-in-Picture mode
  14302. *
  14303. * @extends Button
  14304. */
  14305. var PictureInPictureToggle =
  14306. /*#__PURE__*/
  14307. function (_Button) {
  14308. _inheritsLoose(PictureInPictureToggle, _Button);
  14309. /**
  14310. * Creates an instance of this class.
  14311. *
  14312. * @param {Player} player
  14313. * The `Player` that this class should be attached to.
  14314. *
  14315. * @param {Object} [options]
  14316. * The key/value store of player options.
  14317. *
  14318. * @listens Player#enterpictureinpicture
  14319. * @listens Player#leavepictureinpicture
  14320. */
  14321. function PictureInPictureToggle(player, options) {
  14322. var _this;
  14323. _this = _Button.call(this, player, options) || this;
  14324. _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], _this.handlePictureInPictureChange); // TODO: Activate button on player loadedmetadata event.
  14325. // TODO: Deactivate button on player emptied event.
  14326. // TODO: Deactivate button if disablepictureinpicture attribute is present.
  14327. if (!document.pictureInPictureEnabled) {
  14328. _this.disable();
  14329. }
  14330. return _this;
  14331. }
  14332. /**
  14333. * Builds the default DOM `className`.
  14334. *
  14335. * @return {string}
  14336. * The DOM `className` for this object.
  14337. */
  14338. var _proto = PictureInPictureToggle.prototype;
  14339. _proto.buildCSSClass = function buildCSSClass() {
  14340. return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this);
  14341. }
  14342. /**
  14343. * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
  14344. *
  14345. * @param {EventTarget~Event} [event]
  14346. * The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
  14347. * called.
  14348. *
  14349. * @listens Player#enterpictureinpicture
  14350. * @listens Player#leavepictureinpicture
  14351. */
  14352. ;
  14353. _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) {
  14354. if (this.player_.isInPictureInPicture()) {
  14355. this.controlText('Exit Picture-in-Picture');
  14356. } else {
  14357. this.controlText('Picture-in-Picture');
  14358. }
  14359. }
  14360. /**
  14361. * This gets called when an `PictureInPictureToggle` is "clicked". See
  14362. * {@link ClickableComponent} for more detailed information on what a click can be.
  14363. *
  14364. * @param {EventTarget~Event} [event]
  14365. * The `keydown`, `tap`, or `click` event that caused this function to be
  14366. * called.
  14367. *
  14368. * @listens tap
  14369. * @listens click
  14370. */
  14371. ;
  14372. _proto.handleClick = function handleClick(event) {
  14373. if (!this.player_.isInPictureInPicture()) {
  14374. this.player_.requestPictureInPicture();
  14375. } else {
  14376. this.player_.exitPictureInPicture();
  14377. }
  14378. };
  14379. return PictureInPictureToggle;
  14380. }(Button);
  14381. /**
  14382. * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
  14383. *
  14384. * @type {string}
  14385. * @private
  14386. */
  14387. PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
  14388. Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
  14389. /**
  14390. * Toggle fullscreen video
  14391. *
  14392. * @extends Button
  14393. */
  14394. var FullscreenToggle =
  14395. /*#__PURE__*/
  14396. function (_Button) {
  14397. _inheritsLoose(FullscreenToggle, _Button);
  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 FullscreenToggle(player, options) {
  14408. var _this;
  14409. _this = _Button.call(this, player, options) || this;
  14410. _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
  14411. if (document[player.fsApi_.fullscreenEnabled] === false) {
  14412. _this.disable();
  14413. }
  14414. return _this;
  14415. }
  14416. /**
  14417. * Builds the default DOM `className`.
  14418. *
  14419. * @return {string}
  14420. * The DOM `className` for this object.
  14421. */
  14422. var _proto = FullscreenToggle.prototype;
  14423. _proto.buildCSSClass = function buildCSSClass() {
  14424. return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
  14425. }
  14426. /**
  14427. * Handles fullscreenchange on the player and change control text accordingly.
  14428. *
  14429. * @param {EventTarget~Event} [event]
  14430. * The {@link Player#fullscreenchange} event that caused this function to be
  14431. * called.
  14432. *
  14433. * @listens Player#fullscreenchange
  14434. */
  14435. ;
  14436. _proto.handleFullscreenChange = function handleFullscreenChange(event) {
  14437. if (this.player_.isFullscreen()) {
  14438. this.controlText('Non-Fullscreen');
  14439. } else {
  14440. this.controlText('Fullscreen');
  14441. }
  14442. }
  14443. /**
  14444. * This gets called when an `FullscreenToggle` is "clicked". See
  14445. * {@link ClickableComponent} for more detailed information on what a click can be.
  14446. *
  14447. * @param {EventTarget~Event} [event]
  14448. * The `keydown`, `tap`, or `click` event that caused this function to be
  14449. * called.
  14450. *
  14451. * @listens tap
  14452. * @listens click
  14453. */
  14454. ;
  14455. _proto.handleClick = function handleClick(event) {
  14456. if (!this.player_.isFullscreen()) {
  14457. this.player_.requestFullscreen();
  14458. } else {
  14459. this.player_.exitFullscreen();
  14460. }
  14461. };
  14462. return FullscreenToggle;
  14463. }(Button);
  14464. /**
  14465. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  14466. *
  14467. * @type {string}
  14468. * @private
  14469. */
  14470. FullscreenToggle.prototype.controlText_ = 'Fullscreen';
  14471. Component.registerComponent('FullscreenToggle', FullscreenToggle);
  14472. /**
  14473. * Check if volume control is supported and if it isn't hide the
  14474. * `Component` that was passed using the `vjs-hidden` class.
  14475. *
  14476. * @param {Component} self
  14477. * The component that should be hidden if volume is unsupported
  14478. *
  14479. * @param {Player} player
  14480. * A reference to the player
  14481. *
  14482. * @private
  14483. */
  14484. var checkVolumeSupport = function checkVolumeSupport(self, player) {
  14485. // hide volume controls when they're not supported by the current tech
  14486. if (player.tech_ && !player.tech_.featuresVolumeControl) {
  14487. self.addClass('vjs-hidden');
  14488. }
  14489. self.on(player, 'loadstart', function () {
  14490. if (!player.tech_.featuresVolumeControl) {
  14491. self.addClass('vjs-hidden');
  14492. } else {
  14493. self.removeClass('vjs-hidden');
  14494. }
  14495. });
  14496. };
  14497. /**
  14498. * Shows volume level
  14499. *
  14500. * @extends Component
  14501. */
  14502. var VolumeLevel =
  14503. /*#__PURE__*/
  14504. function (_Component) {
  14505. _inheritsLoose(VolumeLevel, _Component);
  14506. function VolumeLevel() {
  14507. return _Component.apply(this, arguments) || this;
  14508. }
  14509. var _proto = VolumeLevel.prototype;
  14510. /**
  14511. * Create the `Component`'s DOM element
  14512. *
  14513. * @return {Element}
  14514. * The element that was created.
  14515. */
  14516. _proto.createEl = function createEl() {
  14517. return _Component.prototype.createEl.call(this, 'div', {
  14518. className: 'vjs-volume-level',
  14519. innerHTML: '<span class="vjs-control-text"></span>'
  14520. });
  14521. };
  14522. return VolumeLevel;
  14523. }(Component);
  14524. Component.registerComponent('VolumeLevel', VolumeLevel);
  14525. /**
  14526. * The bar that contains the volume level and can be clicked on to adjust the level
  14527. *
  14528. * @extends Slider
  14529. */
  14530. var VolumeBar =
  14531. /*#__PURE__*/
  14532. function (_Slider) {
  14533. _inheritsLoose(VolumeBar, _Slider);
  14534. /**
  14535. * Creates an instance of this class.
  14536. *
  14537. * @param {Player} player
  14538. * The `Player` that this class should be attached to.
  14539. *
  14540. * @param {Object} [options]
  14541. * The key/value store of player options.
  14542. */
  14543. function VolumeBar(player, options) {
  14544. var _this;
  14545. _this = _Slider.call(this, player, options) || this;
  14546. _this.on('slideractive', _this.updateLastVolume_);
  14547. _this.on(player, 'volumechange', _this.updateARIAAttributes);
  14548. player.ready(function () {
  14549. return _this.updateARIAAttributes();
  14550. });
  14551. return _this;
  14552. }
  14553. /**
  14554. * Create the `Component`'s DOM element
  14555. *
  14556. * @return {Element}
  14557. * The element that was created.
  14558. */
  14559. var _proto = VolumeBar.prototype;
  14560. _proto.createEl = function createEl() {
  14561. return _Slider.prototype.createEl.call(this, 'div', {
  14562. className: 'vjs-volume-bar vjs-slider-bar'
  14563. }, {
  14564. 'aria-label': this.localize('Volume Level'),
  14565. 'aria-live': 'polite'
  14566. });
  14567. }
  14568. /**
  14569. * Handle mouse down on volume bar
  14570. *
  14571. * @param {EventTarget~Event} event
  14572. * The `mousedown` event that caused this to run.
  14573. *
  14574. * @listens mousedown
  14575. */
  14576. ;
  14577. _proto.handleMouseDown = function handleMouseDown(event) {
  14578. if (!isSingleLeftClick(event)) {
  14579. return;
  14580. }
  14581. _Slider.prototype.handleMouseDown.call(this, event);
  14582. }
  14583. /**
  14584. * Handle movement events on the {@link VolumeMenuButton}.
  14585. *
  14586. * @param {EventTarget~Event} event
  14587. * The event that caused this function to run.
  14588. *
  14589. * @listens mousemove
  14590. */
  14591. ;
  14592. _proto.handleMouseMove = function handleMouseMove(event) {
  14593. if (!isSingleLeftClick(event)) {
  14594. return;
  14595. }
  14596. this.checkMuted();
  14597. this.player_.volume(this.calculateDistance(event));
  14598. }
  14599. /**
  14600. * If the player is muted unmute it.
  14601. */
  14602. ;
  14603. _proto.checkMuted = function checkMuted() {
  14604. if (this.player_.muted()) {
  14605. this.player_.muted(false);
  14606. }
  14607. }
  14608. /**
  14609. * Get percent of volume level
  14610. *
  14611. * @return {number}
  14612. * Volume level percent as a decimal number.
  14613. */
  14614. ;
  14615. _proto.getPercent = function getPercent() {
  14616. if (this.player_.muted()) {
  14617. return 0;
  14618. }
  14619. return this.player_.volume();
  14620. }
  14621. /**
  14622. * Increase volume level for keyboard users
  14623. */
  14624. ;
  14625. _proto.stepForward = function stepForward() {
  14626. this.checkMuted();
  14627. this.player_.volume(this.player_.volume() + 0.1);
  14628. }
  14629. /**
  14630. * Decrease volume level for keyboard users
  14631. */
  14632. ;
  14633. _proto.stepBack = function stepBack() {
  14634. this.checkMuted();
  14635. this.player_.volume(this.player_.volume() - 0.1);
  14636. }
  14637. /**
  14638. * Update ARIA accessibility attributes
  14639. *
  14640. * @param {EventTarget~Event} [event]
  14641. * The `volumechange` event that caused this function to run.
  14642. *
  14643. * @listens Player#volumechange
  14644. */
  14645. ;
  14646. _proto.updateARIAAttributes = function updateARIAAttributes(event) {
  14647. var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
  14648. this.el_.setAttribute('aria-valuenow', ariaValue);
  14649. this.el_.setAttribute('aria-valuetext', ariaValue + '%');
  14650. }
  14651. /**
  14652. * Returns the current value of the player volume as a percentage
  14653. *
  14654. * @private
  14655. */
  14656. ;
  14657. _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
  14658. return Math.round(this.player_.volume() * 100);
  14659. }
  14660. /**
  14661. * When user starts dragging the VolumeBar, store the volume and listen for
  14662. * the end of the drag. When the drag ends, if the volume was set to zero,
  14663. * set lastVolume to the stored volume.
  14664. *
  14665. * @listens slideractive
  14666. * @private
  14667. */
  14668. ;
  14669. _proto.updateLastVolume_ = function updateLastVolume_() {
  14670. var _this2 = this;
  14671. var volumeBeforeDrag = this.player_.volume();
  14672. this.one('sliderinactive', function () {
  14673. if (_this2.player_.volume() === 0) {
  14674. _this2.player_.lastVolume_(volumeBeforeDrag);
  14675. }
  14676. });
  14677. };
  14678. return VolumeBar;
  14679. }(Slider);
  14680. /**
  14681. * Default options for the `VolumeBar`
  14682. *
  14683. * @type {Object}
  14684. * @private
  14685. */
  14686. VolumeBar.prototype.options_ = {
  14687. children: ['volumeLevel'],
  14688. barName: 'volumeLevel'
  14689. };
  14690. /**
  14691. * Call the update event for this Slider when this event happens on the player.
  14692. *
  14693. * @type {string}
  14694. */
  14695. VolumeBar.prototype.playerEvent = 'volumechange';
  14696. Component.registerComponent('VolumeBar', VolumeBar);
  14697. /**
  14698. * The component for controlling the volume level
  14699. *
  14700. * @extends Component
  14701. */
  14702. var VolumeControl =
  14703. /*#__PURE__*/
  14704. function (_Component) {
  14705. _inheritsLoose(VolumeControl, _Component);
  14706. /**
  14707. * Creates an instance of this class.
  14708. *
  14709. * @param {Player} player
  14710. * The `Player` that this class should be attached to.
  14711. *
  14712. * @param {Object} [options={}]
  14713. * The key/value store of player options.
  14714. */
  14715. function VolumeControl(player, options) {
  14716. var _this;
  14717. if (options === void 0) {
  14718. options = {};
  14719. }
  14720. options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
  14721. // the VolumeBar is turned on.
  14722. if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
  14723. options.volumeBar = options.volumeBar || {};
  14724. options.volumeBar.vertical = options.vertical;
  14725. }
  14726. _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
  14727. checkVolumeSupport(_assertThisInitialized(_this), player);
  14728. _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_this), _this.handleMouseMove), 25);
  14729. _this.on('mousedown', _this.handleMouseDown);
  14730. _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and
  14731. // is dragging) or in focus we do not want to hide the VolumeBar
  14732. _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
  14733. _this.volumeBar.addClass('vjs-slider-active');
  14734. _this.addClass('vjs-slider-active');
  14735. _this.trigger('slideractive');
  14736. });
  14737. _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
  14738. _this.volumeBar.removeClass('vjs-slider-active');
  14739. _this.removeClass('vjs-slider-active');
  14740. _this.trigger('sliderinactive');
  14741. });
  14742. return _this;
  14743. }
  14744. /**
  14745. * Create the `Component`'s DOM element
  14746. *
  14747. * @return {Element}
  14748. * The element that was created.
  14749. */
  14750. var _proto = VolumeControl.prototype;
  14751. _proto.createEl = function createEl() {
  14752. var orientationClass = 'vjs-volume-horizontal';
  14753. if (this.options_.vertical) {
  14754. orientationClass = 'vjs-volume-vertical';
  14755. }
  14756. return _Component.prototype.createEl.call(this, 'div', {
  14757. className: "vjs-volume-control vjs-control " + orientationClass
  14758. });
  14759. }
  14760. /**
  14761. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  14762. *
  14763. * @param {EventTarget~Event} event
  14764. * `mousedown` or `touchstart` event that triggered this function
  14765. *
  14766. * @listens mousedown
  14767. * @listens touchstart
  14768. */
  14769. ;
  14770. _proto.handleMouseDown = function handleMouseDown(event) {
  14771. var doc = this.el_.ownerDocument;
  14772. this.on(doc, 'mousemove', this.throttledHandleMouseMove);
  14773. this.on(doc, 'touchmove', this.throttledHandleMouseMove);
  14774. this.on(doc, 'mouseup', this.handleMouseUp);
  14775. this.on(doc, 'touchend', this.handleMouseUp);
  14776. }
  14777. /**
  14778. * Handle `mouseup` or `touchend` events on the `VolumeControl`.
  14779. *
  14780. * @param {EventTarget~Event} event
  14781. * `mouseup` or `touchend` event that triggered this function.
  14782. *
  14783. * @listens touchend
  14784. * @listens mouseup
  14785. */
  14786. ;
  14787. _proto.handleMouseUp = function handleMouseUp(event) {
  14788. var doc = this.el_.ownerDocument;
  14789. this.off(doc, 'mousemove', this.throttledHandleMouseMove);
  14790. this.off(doc, 'touchmove', this.throttledHandleMouseMove);
  14791. this.off(doc, 'mouseup', this.handleMouseUp);
  14792. this.off(doc, 'touchend', this.handleMouseUp);
  14793. }
  14794. /**
  14795. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  14796. *
  14797. * @param {EventTarget~Event} event
  14798. * `mousedown` or `touchstart` event that triggered this function
  14799. *
  14800. * @listens mousedown
  14801. * @listens touchstart
  14802. */
  14803. ;
  14804. _proto.handleMouseMove = function handleMouseMove(event) {
  14805. this.volumeBar.handleMouseMove(event);
  14806. };
  14807. return VolumeControl;
  14808. }(Component);
  14809. /**
  14810. * Default options for the `VolumeControl`
  14811. *
  14812. * @type {Object}
  14813. * @private
  14814. */
  14815. VolumeControl.prototype.options_ = {
  14816. children: ['volumeBar']
  14817. };
  14818. Component.registerComponent('VolumeControl', VolumeControl);
  14819. /**
  14820. * Check if muting volume is supported and if it isn't hide the mute toggle
  14821. * button.
  14822. *
  14823. * @param {Component} self
  14824. * A reference to the mute toggle button
  14825. *
  14826. * @param {Player} player
  14827. * A reference to the player
  14828. *
  14829. * @private
  14830. */
  14831. var checkMuteSupport = function checkMuteSupport(self, player) {
  14832. // hide mute toggle button if it's not supported by the current tech
  14833. if (player.tech_ && !player.tech_.featuresMuteControl) {
  14834. self.addClass('vjs-hidden');
  14835. }
  14836. self.on(player, 'loadstart', function () {
  14837. if (!player.tech_.featuresMuteControl) {
  14838. self.addClass('vjs-hidden');
  14839. } else {
  14840. self.removeClass('vjs-hidden');
  14841. }
  14842. });
  14843. };
  14844. /**
  14845. * A button component for muting the audio.
  14846. *
  14847. * @extends Button
  14848. */
  14849. var MuteToggle =
  14850. /*#__PURE__*/
  14851. function (_Button) {
  14852. _inheritsLoose(MuteToggle, _Button);
  14853. /**
  14854. * Creates an instance of this class.
  14855. *
  14856. * @param {Player} player
  14857. * The `Player` that this class should be attached to.
  14858. *
  14859. * @param {Object} [options]
  14860. * The key/value store of player options.
  14861. */
  14862. function MuteToggle(player, options) {
  14863. var _this;
  14864. _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
  14865. checkMuteSupport(_assertThisInitialized(_this), player);
  14866. _this.on(player, ['loadstart', 'volumechange'], _this.update);
  14867. return _this;
  14868. }
  14869. /**
  14870. * Builds the default DOM `className`.
  14871. *
  14872. * @return {string}
  14873. * The DOM `className` for this object.
  14874. */
  14875. var _proto = MuteToggle.prototype;
  14876. _proto.buildCSSClass = function buildCSSClass() {
  14877. return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
  14878. }
  14879. /**
  14880. * This gets called when an `MuteToggle` is "clicked". See
  14881. * {@link ClickableComponent} for more detailed information on what a click can be.
  14882. *
  14883. * @param {EventTarget~Event} [event]
  14884. * The `keydown`, `tap`, or `click` event that caused this function to be
  14885. * called.
  14886. *
  14887. * @listens tap
  14888. * @listens click
  14889. */
  14890. ;
  14891. _proto.handleClick = function handleClick(event) {
  14892. var vol = this.player_.volume();
  14893. var lastVolume = this.player_.lastVolume_();
  14894. if (vol === 0) {
  14895. var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
  14896. this.player_.volume(volumeToSet);
  14897. this.player_.muted(false);
  14898. } else {
  14899. this.player_.muted(this.player_.muted() ? false : true);
  14900. }
  14901. }
  14902. /**
  14903. * Update the `MuteToggle` button based on the state of `volume` and `muted`
  14904. * on the player.
  14905. *
  14906. * @param {EventTarget~Event} [event]
  14907. * The {@link Player#loadstart} event if this function was called
  14908. * through an event.
  14909. *
  14910. * @listens Player#loadstart
  14911. * @listens Player#volumechange
  14912. */
  14913. ;
  14914. _proto.update = function update(event) {
  14915. this.updateIcon_();
  14916. this.updateControlText_();
  14917. }
  14918. /**
  14919. * Update the appearance of the `MuteToggle` icon.
  14920. *
  14921. * Possible states (given `level` variable below):
  14922. * - 0: crossed out
  14923. * - 1: zero bars of volume
  14924. * - 2: one bar of volume
  14925. * - 3: two bars of volume
  14926. *
  14927. * @private
  14928. */
  14929. ;
  14930. _proto.updateIcon_ = function updateIcon_() {
  14931. var vol = this.player_.volume();
  14932. var level = 3; // in iOS when a player is loaded with muted attribute
  14933. // and volume is changed with a native mute button
  14934. // we want to make sure muted state is updated
  14935. if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
  14936. this.player_.muted(this.player_.tech_.el_.muted);
  14937. }
  14938. if (vol === 0 || this.player_.muted()) {
  14939. level = 0;
  14940. } else if (vol < 0.33) {
  14941. level = 1;
  14942. } else if (vol < 0.67) {
  14943. level = 2;
  14944. } // TODO improve muted icon classes
  14945. for (var i = 0; i < 4; i++) {
  14946. removeClass(this.el_, "vjs-vol-" + i);
  14947. }
  14948. addClass(this.el_, "vjs-vol-" + level);
  14949. }
  14950. /**
  14951. * If `muted` has changed on the player, update the control text
  14952. * (`title` attribute on `vjs-mute-control` element and content of
  14953. * `vjs-control-text` element).
  14954. *
  14955. * @private
  14956. */
  14957. ;
  14958. _proto.updateControlText_ = function updateControlText_() {
  14959. var soundOff = this.player_.muted() || this.player_.volume() === 0;
  14960. var text = soundOff ? 'Unmute' : 'Mute';
  14961. if (this.controlText() !== text) {
  14962. this.controlText(text);
  14963. }
  14964. };
  14965. return MuteToggle;
  14966. }(Button);
  14967. /**
  14968. * The text that should display over the `MuteToggle`s controls. Added for localization.
  14969. *
  14970. * @type {string}
  14971. * @private
  14972. */
  14973. MuteToggle.prototype.controlText_ = 'Mute';
  14974. Component.registerComponent('MuteToggle', MuteToggle);
  14975. /**
  14976. * A Component to contain the MuteToggle and VolumeControl so that
  14977. * they can work together.
  14978. *
  14979. * @extends Component
  14980. */
  14981. var VolumePanel =
  14982. /*#__PURE__*/
  14983. function (_Component) {
  14984. _inheritsLoose(VolumePanel, _Component);
  14985. /**
  14986. * Creates an instance of this class.
  14987. *
  14988. * @param {Player} player
  14989. * The `Player` that this class should be attached to.
  14990. *
  14991. * @param {Object} [options={}]
  14992. * The key/value store of player options.
  14993. */
  14994. function VolumePanel(player, options) {
  14995. var _this;
  14996. if (options === void 0) {
  14997. options = {};
  14998. }
  14999. if (typeof options.inline !== 'undefined') {
  15000. options.inline = options.inline;
  15001. } else {
  15002. options.inline = true;
  15003. } // pass the inline option down to the VolumeControl as vertical if
  15004. // the VolumeControl is on.
  15005. if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
  15006. options.volumeControl = options.volumeControl || {};
  15007. options.volumeControl.vertical = !options.inline;
  15008. }
  15009. _this = _Component.call(this, player, options) || this;
  15010. _this.on(player, ['loadstart'], _this.volumePanelState_); // while the slider is active (the mouse has been pressed down and
  15011. // is dragging) we do not want to hide the VolumeBar
  15012. _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
  15013. _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
  15014. return _this;
  15015. }
  15016. /**
  15017. * Add vjs-slider-active class to the VolumePanel
  15018. *
  15019. * @listens VolumeControl#slideractive
  15020. * @private
  15021. */
  15022. var _proto = VolumePanel.prototype;
  15023. _proto.sliderActive_ = function sliderActive_() {
  15024. this.addClass('vjs-slider-active');
  15025. }
  15026. /**
  15027. * Removes vjs-slider-active class to the VolumePanel
  15028. *
  15029. * @listens VolumeControl#sliderinactive
  15030. * @private
  15031. */
  15032. ;
  15033. _proto.sliderInactive_ = function sliderInactive_() {
  15034. this.removeClass('vjs-slider-active');
  15035. }
  15036. /**
  15037. * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
  15038. * depending on MuteToggle and VolumeControl state
  15039. *
  15040. * @listens Player#loadstart
  15041. * @private
  15042. */
  15043. ;
  15044. _proto.volumePanelState_ = function volumePanelState_() {
  15045. // hide volume panel if neither volume control or mute toggle
  15046. // are displayed
  15047. if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
  15048. this.addClass('vjs-hidden');
  15049. } // if only mute toggle is visible we don't want
  15050. // volume panel expanding when hovered or active
  15051. if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
  15052. this.addClass('vjs-mute-toggle-only');
  15053. }
  15054. }
  15055. /**
  15056. * Create the `Component`'s DOM element
  15057. *
  15058. * @return {Element}
  15059. * The element that was created.
  15060. */
  15061. ;
  15062. _proto.createEl = function createEl() {
  15063. var orientationClass = 'vjs-volume-panel-horizontal';
  15064. if (!this.options_.inline) {
  15065. orientationClass = 'vjs-volume-panel-vertical';
  15066. }
  15067. return _Component.prototype.createEl.call(this, 'div', {
  15068. className: "vjs-volume-panel vjs-control " + orientationClass
  15069. });
  15070. };
  15071. return VolumePanel;
  15072. }(Component);
  15073. /**
  15074. * Default options for the `VolumeControl`
  15075. *
  15076. * @type {Object}
  15077. * @private
  15078. */
  15079. VolumePanel.prototype.options_ = {
  15080. children: ['muteToggle', 'volumeControl']
  15081. };
  15082. Component.registerComponent('VolumePanel', VolumePanel);
  15083. /**
  15084. * The Menu component is used to build popup menus, including subtitle and
  15085. * captions selection menus.
  15086. *
  15087. * @extends Component
  15088. */
  15089. var Menu =
  15090. /*#__PURE__*/
  15091. function (_Component) {
  15092. _inheritsLoose(Menu, _Component);
  15093. /**
  15094. * Create an instance of this class.
  15095. *
  15096. * @param {Player} player
  15097. * the player that this component should attach to
  15098. *
  15099. * @param {Object} [options]
  15100. * Object of option names and values
  15101. *
  15102. */
  15103. function Menu(player, options) {
  15104. var _this;
  15105. _this = _Component.call(this, player, options) || this;
  15106. if (options) {
  15107. _this.menuButton_ = options.menuButton;
  15108. }
  15109. _this.focusedChild_ = -1;
  15110. _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container.
  15111. _this.boundHandleBlur_ = bind(_assertThisInitialized(_this), _this.handleBlur);
  15112. _this.boundHandleTapClick_ = bind(_assertThisInitialized(_this), _this.handleTapClick);
  15113. return _this;
  15114. }
  15115. /**
  15116. * Add event listeners to the {@link MenuItem}.
  15117. *
  15118. * @param {Object} component
  15119. * The instance of the `MenuItem` to add listeners to.
  15120. *
  15121. */
  15122. var _proto = Menu.prototype;
  15123. _proto.addEventListenerForItem = function addEventListenerForItem(component) {
  15124. if (!(component instanceof Component)) {
  15125. return;
  15126. }
  15127. this.on(component, 'blur', this.boundHandleBlur_);
  15128. this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
  15129. }
  15130. /**
  15131. * Remove event listeners from the {@link MenuItem}.
  15132. *
  15133. * @param {Object} component
  15134. * The instance of the `MenuItem` to remove listeners.
  15135. *
  15136. */
  15137. ;
  15138. _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
  15139. if (!(component instanceof Component)) {
  15140. return;
  15141. }
  15142. this.off(component, 'blur', this.boundHandleBlur_);
  15143. this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
  15144. }
  15145. /**
  15146. * This method will be called indirectly when the component has been added
  15147. * before the component adds to the new menu instance by `addItem`.
  15148. * In this case, the original menu instance will remove the component
  15149. * by calling `removeChild`.
  15150. *
  15151. * @param {Object} component
  15152. * The instance of the `MenuItem`
  15153. */
  15154. ;
  15155. _proto.removeChild = function removeChild(component) {
  15156. if (typeof component === 'string') {
  15157. component = this.getChild(component);
  15158. }
  15159. this.removeEventListenerForItem(component);
  15160. _Component.prototype.removeChild.call(this, component);
  15161. }
  15162. /**
  15163. * Add a {@link MenuItem} to the menu.
  15164. *
  15165. * @param {Object|string} component
  15166. * The name or instance of the `MenuItem` to add.
  15167. *
  15168. */
  15169. ;
  15170. _proto.addItem = function addItem(component) {
  15171. var childComponent = this.addChild(component);
  15172. if (childComponent) {
  15173. this.addEventListenerForItem(childComponent);
  15174. }
  15175. }
  15176. /**
  15177. * Create the `Menu`s DOM element.
  15178. *
  15179. * @return {Element}
  15180. * the element that was created
  15181. */
  15182. ;
  15183. _proto.createEl = function createEl$1() {
  15184. var contentElType = this.options_.contentElType || 'ul';
  15185. this.contentEl_ = createEl(contentElType, {
  15186. className: 'vjs-menu-content'
  15187. });
  15188. this.contentEl_.setAttribute('role', 'menu');
  15189. var el = _Component.prototype.createEl.call(this, 'div', {
  15190. append: this.contentEl_,
  15191. className: 'vjs-menu'
  15192. });
  15193. el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
  15194. // where a click on the parent is significant
  15195. on(el, 'click', function (event) {
  15196. event.preventDefault();
  15197. event.stopImmediatePropagation();
  15198. });
  15199. return el;
  15200. };
  15201. _proto.dispose = function dispose() {
  15202. this.contentEl_ = null;
  15203. this.boundHandleBlur_ = null;
  15204. this.boundHandleTapClick_ = null;
  15205. _Component.prototype.dispose.call(this);
  15206. }
  15207. /**
  15208. * Called when a `MenuItem` loses focus.
  15209. *
  15210. * @param {EventTarget~Event} event
  15211. * The `blur` event that caused this function to be called.
  15212. *
  15213. * @listens blur
  15214. */
  15215. ;
  15216. _proto.handleBlur = function handleBlur(event) {
  15217. var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
  15218. if (!this.children().some(function (element) {
  15219. return element.el() === relatedTarget;
  15220. })) {
  15221. var btn = this.menuButton_;
  15222. if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
  15223. btn.unpressButton();
  15224. }
  15225. }
  15226. }
  15227. /**
  15228. * Called when a `MenuItem` gets clicked or tapped.
  15229. *
  15230. * @param {EventTarget~Event} event
  15231. * The `click` or `tap` event that caused this function to be called.
  15232. *
  15233. * @listens click,tap
  15234. */
  15235. ;
  15236. _proto.handleTapClick = function handleTapClick(event) {
  15237. // Unpress the associated MenuButton, and move focus back to it
  15238. if (this.menuButton_) {
  15239. this.menuButton_.unpressButton();
  15240. var childComponents = this.children();
  15241. if (!Array.isArray(childComponents)) {
  15242. return;
  15243. }
  15244. var foundComponent = childComponents.filter(function (component) {
  15245. return component.el() === event.target;
  15246. })[0];
  15247. if (!foundComponent) {
  15248. return;
  15249. } // don't focus menu button if item is a caption settings item
  15250. // because focus will move elsewhere
  15251. if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
  15252. this.menuButton_.focus();
  15253. }
  15254. }
  15255. }
  15256. /**
  15257. * Handle a `keydown` event on this menu. This listener is added in the constructor.
  15258. *
  15259. * @param {EventTarget~Event} event
  15260. * A `keydown` event that happened on the menu.
  15261. *
  15262. * @listens keydown
  15263. */
  15264. ;
  15265. _proto.handleKeyDown = function handleKeyDown(event) {
  15266. // Left and Down Arrows
  15267. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  15268. event.preventDefault();
  15269. event.stopPropagation();
  15270. this.stepForward(); // Up and Right Arrows
  15271. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  15272. event.preventDefault();
  15273. event.stopPropagation();
  15274. this.stepBack();
  15275. }
  15276. }
  15277. /**
  15278. * Move to next (lower) menu item for keyboard users.
  15279. */
  15280. ;
  15281. _proto.stepForward = function stepForward() {
  15282. var stepChild = 0;
  15283. if (this.focusedChild_ !== undefined) {
  15284. stepChild = this.focusedChild_ + 1;
  15285. }
  15286. this.focus(stepChild);
  15287. }
  15288. /**
  15289. * Move to previous (higher) menu item for keyboard users.
  15290. */
  15291. ;
  15292. _proto.stepBack = function stepBack() {
  15293. var stepChild = 0;
  15294. if (this.focusedChild_ !== undefined) {
  15295. stepChild = this.focusedChild_ - 1;
  15296. }
  15297. this.focus(stepChild);
  15298. }
  15299. /**
  15300. * Set focus on a {@link MenuItem} in the `Menu`.
  15301. *
  15302. * @param {Object|string} [item=0]
  15303. * Index of child item set focus on.
  15304. */
  15305. ;
  15306. _proto.focus = function focus(item) {
  15307. if (item === void 0) {
  15308. item = 0;
  15309. }
  15310. var children = this.children().slice();
  15311. var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
  15312. if (haveTitle) {
  15313. children.shift();
  15314. }
  15315. if (children.length > 0) {
  15316. if (item < 0) {
  15317. item = 0;
  15318. } else if (item >= children.length) {
  15319. item = children.length - 1;
  15320. }
  15321. this.focusedChild_ = item;
  15322. children[item].el_.focus();
  15323. }
  15324. };
  15325. return Menu;
  15326. }(Component);
  15327. Component.registerComponent('Menu', Menu);
  15328. /**
  15329. * A `MenuButton` class for any popup {@link Menu}.
  15330. *
  15331. * @extends Component
  15332. */
  15333. var MenuButton =
  15334. /*#__PURE__*/
  15335. function (_Component) {
  15336. _inheritsLoose(MenuButton, _Component);
  15337. /**
  15338. * Creates an instance of this class.
  15339. *
  15340. * @param {Player} player
  15341. * The `Player` that this class should be attached to.
  15342. *
  15343. * @param {Object} [options={}]
  15344. * The key/value store of player options.
  15345. */
  15346. function MenuButton(player, options) {
  15347. var _this;
  15348. if (options === void 0) {
  15349. options = {};
  15350. }
  15351. _this = _Component.call(this, player, options) || this;
  15352. _this.menuButton_ = new Button(player, options);
  15353. _this.menuButton_.controlText(_this.controlText_);
  15354. _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
  15355. var buttonClass = Button.prototype.buildCSSClass();
  15356. _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
  15357. _this.menuButton_.removeClass('vjs-control');
  15358. _this.addChild(_this.menuButton_);
  15359. _this.update();
  15360. _this.enabled_ = true;
  15361. _this.on(_this.menuButton_, 'tap', _this.handleClick);
  15362. _this.on(_this.menuButton_, 'click', _this.handleClick);
  15363. _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown);
  15364. _this.on(_this.menuButton_, 'mouseenter', function () {
  15365. _this.menu.show();
  15366. });
  15367. _this.on('keydown', _this.handleSubmenuKeyDown);
  15368. return _this;
  15369. }
  15370. /**
  15371. * Update the menu based on the current state of its items.
  15372. */
  15373. var _proto = MenuButton.prototype;
  15374. _proto.update = function update() {
  15375. var menu = this.createMenu();
  15376. if (this.menu) {
  15377. this.menu.dispose();
  15378. this.removeChild(this.menu);
  15379. }
  15380. this.menu = menu;
  15381. this.addChild(menu);
  15382. /**
  15383. * Track the state of the menu button
  15384. *
  15385. * @type {Boolean}
  15386. * @private
  15387. */
  15388. this.buttonPressed_ = false;
  15389. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  15390. if (this.items && this.items.length <= this.hideThreshold_) {
  15391. this.hide();
  15392. } else {
  15393. this.show();
  15394. }
  15395. }
  15396. /**
  15397. * Create the menu and add all items to it.
  15398. *
  15399. * @return {Menu}
  15400. * The constructed menu
  15401. */
  15402. ;
  15403. _proto.createMenu = function createMenu() {
  15404. var menu = new Menu(this.player_, {
  15405. menuButton: this
  15406. });
  15407. /**
  15408. * Hide the menu if the number of items is less than or equal to this threshold. This defaults
  15409. * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
  15410. * it here because every time we run `createMenu` we need to reset the value.
  15411. *
  15412. * @protected
  15413. * @type {Number}
  15414. */
  15415. this.hideThreshold_ = 0; // Add a title list item to the top
  15416. if (this.options_.title) {
  15417. var titleEl = createEl('li', {
  15418. className: 'vjs-menu-title',
  15419. innerHTML: toTitleCase(this.options_.title),
  15420. tabIndex: -1
  15421. });
  15422. this.hideThreshold_ += 1;
  15423. var titleComponent = new Component(this.player_, {
  15424. el: titleEl
  15425. });
  15426. menu.addItem(titleComponent);
  15427. }
  15428. this.items = this.createItems();
  15429. if (this.items) {
  15430. // Add menu items to the menu
  15431. for (var i = 0; i < this.items.length; i++) {
  15432. menu.addItem(this.items[i]);
  15433. }
  15434. }
  15435. return menu;
  15436. }
  15437. /**
  15438. * Create the list of menu items. Specific to each subclass.
  15439. *
  15440. * @abstract
  15441. */
  15442. ;
  15443. _proto.createItems = function createItems() {}
  15444. /**
  15445. * Create the `MenuButtons`s DOM element.
  15446. *
  15447. * @return {Element}
  15448. * The element that gets created.
  15449. */
  15450. ;
  15451. _proto.createEl = function createEl() {
  15452. return _Component.prototype.createEl.call(this, 'div', {
  15453. className: this.buildWrapperCSSClass()
  15454. }, {});
  15455. }
  15456. /**
  15457. * Allow sub components to stack CSS class names for the wrapper element
  15458. *
  15459. * @return {string}
  15460. * The constructed wrapper DOM `className`
  15461. */
  15462. ;
  15463. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15464. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  15465. if (this.options_.inline === true) {
  15466. menuButtonClass += '-inline';
  15467. } else {
  15468. menuButtonClass += '-popup';
  15469. } // TODO: Fix the CSS so that this isn't necessary
  15470. var buttonClass = Button.prototype.buildCSSClass();
  15471. return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
  15472. }
  15473. /**
  15474. * Builds the default DOM `className`.
  15475. *
  15476. * @return {string}
  15477. * The DOM `className` for this object.
  15478. */
  15479. ;
  15480. _proto.buildCSSClass = function buildCSSClass() {
  15481. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  15482. if (this.options_.inline === true) {
  15483. menuButtonClass += '-inline';
  15484. } else {
  15485. menuButtonClass += '-popup';
  15486. }
  15487. return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
  15488. }
  15489. /**
  15490. * Get or set the localized control text that will be used for accessibility.
  15491. *
  15492. * > NOTE: This will come from the internal `menuButton_` element.
  15493. *
  15494. * @param {string} [text]
  15495. * Control text for element.
  15496. *
  15497. * @param {Element} [el=this.menuButton_.el()]
  15498. * Element to set the title on.
  15499. *
  15500. * @return {string}
  15501. * - The control text when getting
  15502. */
  15503. ;
  15504. _proto.controlText = function controlText(text, el) {
  15505. if (el === void 0) {
  15506. el = this.menuButton_.el();
  15507. }
  15508. return this.menuButton_.controlText(text, el);
  15509. }
  15510. /**
  15511. * Handle a click on a `MenuButton`.
  15512. * See {@link ClickableComponent#handleClick} for instances where this is called.
  15513. *
  15514. * @param {EventTarget~Event} event
  15515. * The `keydown`, `tap`, or `click` event that caused this function to be
  15516. * called.
  15517. *
  15518. * @listens tap
  15519. * @listens click
  15520. */
  15521. ;
  15522. _proto.handleClick = function handleClick(event) {
  15523. if (this.buttonPressed_) {
  15524. this.unpressButton();
  15525. } else {
  15526. this.pressButton();
  15527. }
  15528. }
  15529. /**
  15530. * Set the focus to the actual button, not to this element
  15531. */
  15532. ;
  15533. _proto.focus = function focus() {
  15534. this.menuButton_.focus();
  15535. }
  15536. /**
  15537. * Remove the focus from the actual button, not this element
  15538. */
  15539. ;
  15540. _proto.blur = function blur() {
  15541. this.menuButton_.blur();
  15542. }
  15543. /**
  15544. * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
  15545. * {@link ClickableComponent#handleKeyDown} for instances where this is called.
  15546. *
  15547. * @param {EventTarget~Event} event
  15548. * The `keydown` event that caused this function to be called.
  15549. *
  15550. * @listens keydown
  15551. */
  15552. ;
  15553. _proto.handleKeyDown = function handleKeyDown(event) {
  15554. // Escape or Tab unpress the 'button'
  15555. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  15556. if (this.buttonPressed_) {
  15557. this.unpressButton();
  15558. } // Don't preventDefault for Tab key - we still want to lose focus
  15559. if (!keycode.isEventKey(event, 'Tab')) {
  15560. event.preventDefault(); // Set focus back to the menu button's button
  15561. this.menuButton_.focus();
  15562. } // Up Arrow or Down Arrow also 'press' the button to open the menu
  15563. } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
  15564. if (!this.buttonPressed_) {
  15565. event.preventDefault();
  15566. this.pressButton();
  15567. }
  15568. }
  15569. }
  15570. /**
  15571. * This method name now delegates to `handleSubmenuKeyDown`. This means
  15572. * anyone calling `handleSubmenuKeyPress` will not see their method calls
  15573. * stop working.
  15574. *
  15575. * @param {EventTarget~Event} event
  15576. * The event that caused this function to be called.
  15577. */
  15578. ;
  15579. _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
  15580. this.handleSubmenuKeyDown(event);
  15581. }
  15582. /**
  15583. * Handle a `keydown` event on a sub-menu. The listener for this is added in
  15584. * the constructor.
  15585. *
  15586. * @param {EventTarget~Event} event
  15587. * Key press event
  15588. *
  15589. * @listens keydown
  15590. */
  15591. ;
  15592. _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) {
  15593. // Escape or Tab unpress the 'button'
  15594. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  15595. if (this.buttonPressed_) {
  15596. this.unpressButton();
  15597. } // Don't preventDefault for Tab key - we still want to lose focus
  15598. if (!keycode.isEventKey(event, 'Tab')) {
  15599. event.preventDefault(); // Set focus back to the menu button's button
  15600. this.menuButton_.focus();
  15601. }
  15602. }
  15603. }
  15604. /**
  15605. * Put the current `MenuButton` into a pressed state.
  15606. */
  15607. ;
  15608. _proto.pressButton = function pressButton() {
  15609. if (this.enabled_) {
  15610. this.buttonPressed_ = true;
  15611. this.menu.show();
  15612. this.menu.lockShowing();
  15613. this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
  15614. // undesired scrolling behavior when the player is in an iframe
  15615. if (IS_IOS && isInFrame()) {
  15616. // Return early so that the menu isn't focused
  15617. return;
  15618. }
  15619. this.menu.focus();
  15620. }
  15621. }
  15622. /**
  15623. * Take the current `MenuButton` out of a pressed state.
  15624. */
  15625. ;
  15626. _proto.unpressButton = function unpressButton() {
  15627. if (this.enabled_) {
  15628. this.buttonPressed_ = false;
  15629. this.menu.unlockShowing();
  15630. this.menu.hide();
  15631. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  15632. }
  15633. }
  15634. /**
  15635. * Disable the `MenuButton`. Don't allow it to be clicked.
  15636. */
  15637. ;
  15638. _proto.disable = function disable() {
  15639. this.unpressButton();
  15640. this.enabled_ = false;
  15641. this.addClass('vjs-disabled');
  15642. this.menuButton_.disable();
  15643. }
  15644. /**
  15645. * Enable the `MenuButton`. Allow it to be clicked.
  15646. */
  15647. ;
  15648. _proto.enable = function enable() {
  15649. this.enabled_ = true;
  15650. this.removeClass('vjs-disabled');
  15651. this.menuButton_.enable();
  15652. };
  15653. return MenuButton;
  15654. }(Component);
  15655. Component.registerComponent('MenuButton', MenuButton);
  15656. /**
  15657. * The base class for buttons that toggle specific track types (e.g. subtitles).
  15658. *
  15659. * @extends MenuButton
  15660. */
  15661. var TrackButton =
  15662. /*#__PURE__*/
  15663. function (_MenuButton) {
  15664. _inheritsLoose(TrackButton, _MenuButton);
  15665. /**
  15666. * Creates an instance of this class.
  15667. *
  15668. * @param {Player} player
  15669. * The `Player` that this class should be attached to.
  15670. *
  15671. * @param {Object} [options]
  15672. * The key/value store of player options.
  15673. */
  15674. function TrackButton(player, options) {
  15675. var _this;
  15676. var tracks = options.tracks;
  15677. _this = _MenuButton.call(this, player, options) || this;
  15678. if (_this.items.length <= 1) {
  15679. _this.hide();
  15680. }
  15681. if (!tracks) {
  15682. return _assertThisInitialized(_this);
  15683. }
  15684. var updateHandler = bind(_assertThisInitialized(_this), _this.update);
  15685. tracks.addEventListener('removetrack', updateHandler);
  15686. tracks.addEventListener('addtrack', updateHandler);
  15687. _this.player_.on('ready', updateHandler);
  15688. _this.player_.on('dispose', function () {
  15689. tracks.removeEventListener('removetrack', updateHandler);
  15690. tracks.removeEventListener('addtrack', updateHandler);
  15691. });
  15692. return _this;
  15693. }
  15694. return TrackButton;
  15695. }(MenuButton);
  15696. Component.registerComponent('TrackButton', TrackButton);
  15697. /**
  15698. * @file menu-keys.js
  15699. */
  15700. /**
  15701. * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
  15702. * Note that 'Enter' and 'Space' are not included here (otherwise they would
  15703. * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
  15704. * @typedef MenuKeys
  15705. * @array
  15706. */
  15707. var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
  15708. /**
  15709. * The component for a menu item. `<li>`
  15710. *
  15711. * @extends ClickableComponent
  15712. */
  15713. var MenuItem =
  15714. /*#__PURE__*/
  15715. function (_ClickableComponent) {
  15716. _inheritsLoose(MenuItem, _ClickableComponent);
  15717. /**
  15718. * Creates an instance of the this class.
  15719. *
  15720. * @param {Player} player
  15721. * The `Player` that this class should be attached to.
  15722. *
  15723. * @param {Object} [options={}]
  15724. * The key/value store of player options.
  15725. *
  15726. */
  15727. function MenuItem(player, options) {
  15728. var _this;
  15729. _this = _ClickableComponent.call(this, player, options) || this;
  15730. _this.selectable = options.selectable;
  15731. _this.isSelected_ = options.selected || false;
  15732. _this.multiSelectable = options.multiSelectable;
  15733. _this.selected(_this.isSelected_);
  15734. if (_this.selectable) {
  15735. if (_this.multiSelectable) {
  15736. _this.el_.setAttribute('role', 'menuitemcheckbox');
  15737. } else {
  15738. _this.el_.setAttribute('role', 'menuitemradio');
  15739. }
  15740. } else {
  15741. _this.el_.setAttribute('role', 'menuitem');
  15742. }
  15743. return _this;
  15744. }
  15745. /**
  15746. * Create the `MenuItem's DOM element
  15747. *
  15748. * @param {string} [type=li]
  15749. * Element's node type, not actually used, always set to `li`.
  15750. *
  15751. * @param {Object} [props={}]
  15752. * An object of properties that should be set on the element
  15753. *
  15754. * @param {Object} [attrs={}]
  15755. * An object of attributes that should be set on the element
  15756. *
  15757. * @return {Element}
  15758. * The element that gets created.
  15759. */
  15760. var _proto = MenuItem.prototype;
  15761. _proto.createEl = function createEl(type, props, attrs) {
  15762. // The control is textual, not just an icon
  15763. this.nonIconControl = true;
  15764. return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
  15765. className: 'vjs-menu-item',
  15766. innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>",
  15767. tabIndex: -1
  15768. }, props), attrs);
  15769. }
  15770. /**
  15771. * Ignore keys which are used by the menu, but pass any other ones up. See
  15772. * {@link ClickableComponent#handleKeyDown} for instances where this is called.
  15773. *
  15774. * @param {EventTarget~Event} event
  15775. * The `keydown` event that caused this function to be called.
  15776. *
  15777. * @listens keydown
  15778. */
  15779. ;
  15780. _proto.handleKeyDown = function handleKeyDown(event) {
  15781. if (!MenuKeys.some(function (key) {
  15782. return keycode.isEventKey(event, key);
  15783. })) {
  15784. // Pass keydown handling up for unused keys
  15785. _ClickableComponent.prototype.handleKeyDown.call(this, event);
  15786. }
  15787. }
  15788. /**
  15789. * Any click on a `MenuItem` puts it into the selected state.
  15790. * See {@link ClickableComponent#handleClick} for instances where this is called.
  15791. *
  15792. * @param {EventTarget~Event} event
  15793. * The `keydown`, `tap`, or `click` event that caused this function to be
  15794. * called.
  15795. *
  15796. * @listens tap
  15797. * @listens click
  15798. */
  15799. ;
  15800. _proto.handleClick = function handleClick(event) {
  15801. this.selected(true);
  15802. }
  15803. /**
  15804. * Set the state for this menu item as selected or not.
  15805. *
  15806. * @param {boolean} selected
  15807. * if the menu item is selected or not
  15808. */
  15809. ;
  15810. _proto.selected = function selected(_selected) {
  15811. if (this.selectable) {
  15812. if (_selected) {
  15813. this.addClass('vjs-selected');
  15814. this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
  15815. // so indicate selected state to screen reader in the control text.
  15816. this.controlText(', selected');
  15817. this.isSelected_ = true;
  15818. } else {
  15819. this.removeClass('vjs-selected');
  15820. this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
  15821. this.controlText('');
  15822. this.isSelected_ = false;
  15823. }
  15824. }
  15825. };
  15826. return MenuItem;
  15827. }(ClickableComponent);
  15828. Component.registerComponent('MenuItem', MenuItem);
  15829. /**
  15830. * The specific menu item type for selecting a language within a text track kind
  15831. *
  15832. * @extends MenuItem
  15833. */
  15834. var TextTrackMenuItem =
  15835. /*#__PURE__*/
  15836. function (_MenuItem) {
  15837. _inheritsLoose(TextTrackMenuItem, _MenuItem);
  15838. /**
  15839. * Creates an instance of this class.
  15840. *
  15841. * @param {Player} player
  15842. * The `Player` that this class should be attached to.
  15843. *
  15844. * @param {Object} [options]
  15845. * The key/value store of player options.
  15846. */
  15847. function TextTrackMenuItem(player, options) {
  15848. var _this;
  15849. var track = options.track;
  15850. var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
  15851. options.label = track.label || track.language || 'Unknown';
  15852. options.selected = track.mode === 'showing';
  15853. _this = _MenuItem.call(this, player, options) || this;
  15854. _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter
  15855. // out empty kinds.
  15856. _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean);
  15857. var changeHandler = function changeHandler() {
  15858. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  15859. args[_key] = arguments[_key];
  15860. }
  15861. _this.handleTracksChange.apply(_assertThisInitialized(_this), args);
  15862. };
  15863. var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
  15864. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  15865. args[_key2] = arguments[_key2];
  15866. }
  15867. _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_this), args);
  15868. };
  15869. player.on(['loadstart', 'texttrackchange'], changeHandler);
  15870. tracks.addEventListener('change', changeHandler);
  15871. tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  15872. _this.on('dispose', function () {
  15873. player.off(['loadstart', 'texttrackchange'], changeHandler);
  15874. tracks.removeEventListener('change', changeHandler);
  15875. tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  15876. }); // iOS7 doesn't dispatch change events to TextTrackLists when an
  15877. // associated track's mode changes. Without something like
  15878. // Object.observe() (also not present on iOS7), it's not
  15879. // possible to detect changes to the mode attribute and polyfill
  15880. // the change event. As a poor substitute, we manually dispatch
  15881. // change events whenever the controls modify the mode.
  15882. if (tracks.onchange === undefined) {
  15883. var event;
  15884. _this.on(['tap', 'click'], function () {
  15885. if (typeof window$1.Event !== 'object') {
  15886. // Android 2.3 throws an Illegal Constructor error for window.Event
  15887. try {
  15888. event = new window$1.Event('change');
  15889. } catch (err) {// continue regardless of error
  15890. }
  15891. }
  15892. if (!event) {
  15893. event = document.createEvent('Event');
  15894. event.initEvent('change', true, true);
  15895. }
  15896. tracks.dispatchEvent(event);
  15897. });
  15898. } // set the default state based on current tracks
  15899. _this.handleTracksChange();
  15900. return _this;
  15901. }
  15902. /**
  15903. * This gets called when an `TextTrackMenuItem` is "clicked". See
  15904. * {@link ClickableComponent} for more detailed information on what a click can be.
  15905. *
  15906. * @param {EventTarget~Event} event
  15907. * The `keydown`, `tap`, or `click` event that caused this function to be
  15908. * called.
  15909. *
  15910. * @listens tap
  15911. * @listens click
  15912. */
  15913. var _proto = TextTrackMenuItem.prototype;
  15914. _proto.handleClick = function handleClick(event) {
  15915. var referenceTrack = this.track;
  15916. var tracks = this.player_.textTracks();
  15917. _MenuItem.prototype.handleClick.call(this, event);
  15918. if (!tracks) {
  15919. return;
  15920. }
  15921. for (var i = 0; i < tracks.length; i++) {
  15922. var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
  15923. // skip it. We do not want to affect tracks of incompatible kind(s).
  15924. if (this.kinds.indexOf(track.kind) === -1) {
  15925. continue;
  15926. } // If this text track is the component's track and it is not showing,
  15927. // set it to showing.
  15928. if (track === referenceTrack) {
  15929. if (track.mode !== 'showing') {
  15930. track.mode = 'showing';
  15931. } // If this text track is not the component's track and it is not
  15932. // disabled, set it to disabled.
  15933. } else if (track.mode !== 'disabled') {
  15934. track.mode = 'disabled';
  15935. }
  15936. }
  15937. }
  15938. /**
  15939. * Handle text track list change
  15940. *
  15941. * @param {EventTarget~Event} event
  15942. * The `change` event that caused this function to be called.
  15943. *
  15944. * @listens TextTrackList#change
  15945. */
  15946. ;
  15947. _proto.handleTracksChange = function handleTracksChange(event) {
  15948. var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
  15949. // screen readers to read the appended control text unnecessarily
  15950. if (shouldBeSelected !== this.isSelected_) {
  15951. this.selected(shouldBeSelected);
  15952. }
  15953. };
  15954. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  15955. if (this.track.mode === 'showing') {
  15956. var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
  15957. if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
  15958. return;
  15959. }
  15960. this.player_.cache_.selectedLanguage = {
  15961. enabled: true,
  15962. language: this.track.language,
  15963. kind: this.track.kind
  15964. };
  15965. }
  15966. };
  15967. _proto.dispose = function dispose() {
  15968. // remove reference to track object on dispose
  15969. this.track = null;
  15970. _MenuItem.prototype.dispose.call(this);
  15971. };
  15972. return TextTrackMenuItem;
  15973. }(MenuItem);
  15974. Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
  15975. /**
  15976. * A special menu item for turning of a specific type of text track
  15977. *
  15978. * @extends TextTrackMenuItem
  15979. */
  15980. var OffTextTrackMenuItem =
  15981. /*#__PURE__*/
  15982. function (_TextTrackMenuItem) {
  15983. _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
  15984. /**
  15985. * Creates an instance of this class.
  15986. *
  15987. * @param {Player} player
  15988. * The `Player` that this class should be attached to.
  15989. *
  15990. * @param {Object} [options]
  15991. * The key/value store of player options.
  15992. */
  15993. function OffTextTrackMenuItem(player, options) {
  15994. // Create pseudo track info
  15995. // Requires options['kind']
  15996. options.track = {
  15997. player: player,
  15998. // it is no longer necessary to store `kind` or `kinds` on the track itself
  15999. // since they are now stored in the `kinds` property of all instances of
  16000. // TextTrackMenuItem, but this will remain for backwards compatibility
  16001. kind: options.kind,
  16002. kinds: options.kinds,
  16003. "default": false,
  16004. mode: 'disabled'
  16005. };
  16006. if (!options.kinds) {
  16007. options.kinds = [options.kind];
  16008. }
  16009. if (options.label) {
  16010. options.track.label = options.label;
  16011. } else {
  16012. options.track.label = options.kinds.join(' and ') + ' off';
  16013. } // MenuItem is selectable
  16014. options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  16015. options.multiSelectable = false;
  16016. return _TextTrackMenuItem.call(this, player, options) || this;
  16017. }
  16018. /**
  16019. * Handle text track change
  16020. *
  16021. * @param {EventTarget~Event} event
  16022. * The event that caused this function to run
  16023. */
  16024. var _proto = OffTextTrackMenuItem.prototype;
  16025. _proto.handleTracksChange = function handleTracksChange(event) {
  16026. var tracks = this.player().textTracks();
  16027. var shouldBeSelected = true;
  16028. for (var i = 0, l = tracks.length; i < l; i++) {
  16029. var track = tracks[i];
  16030. if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
  16031. shouldBeSelected = false;
  16032. break;
  16033. }
  16034. } // Prevent redundant selected() calls because they may cause
  16035. // screen readers to read the appended control text unnecessarily
  16036. if (shouldBeSelected !== this.isSelected_) {
  16037. this.selected(shouldBeSelected);
  16038. }
  16039. };
  16040. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  16041. var tracks = this.player().textTracks();
  16042. var allHidden = true;
  16043. for (var i = 0, l = tracks.length; i < l; i++) {
  16044. var track = tracks[i];
  16045. if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
  16046. allHidden = false;
  16047. break;
  16048. }
  16049. }
  16050. if (allHidden) {
  16051. this.player_.cache_.selectedLanguage = {
  16052. enabled: false
  16053. };
  16054. }
  16055. };
  16056. return OffTextTrackMenuItem;
  16057. }(TextTrackMenuItem);
  16058. Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
  16059. /**
  16060. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  16061. *
  16062. * @extends MenuButton
  16063. */
  16064. var TextTrackButton =
  16065. /*#__PURE__*/
  16066. function (_TrackButton) {
  16067. _inheritsLoose(TextTrackButton, _TrackButton);
  16068. /**
  16069. * Creates an instance of this class.
  16070. *
  16071. * @param {Player} player
  16072. * The `Player` that this class should be attached to.
  16073. *
  16074. * @param {Object} [options={}]
  16075. * The key/value store of player options.
  16076. */
  16077. function TextTrackButton(player, options) {
  16078. if (options === void 0) {
  16079. options = {};
  16080. }
  16081. options.tracks = player.textTracks();
  16082. return _TrackButton.call(this, player, options) || this;
  16083. }
  16084. /**
  16085. * Create a menu item for each text track
  16086. *
  16087. * @param {TextTrackMenuItem[]} [items=[]]
  16088. * Existing array of items to use during creation
  16089. *
  16090. * @return {TextTrackMenuItem[]}
  16091. * Array of menu items that were created
  16092. */
  16093. var _proto = TextTrackButton.prototype;
  16094. _proto.createItems = function createItems(items, TrackMenuItem) {
  16095. if (items === void 0) {
  16096. items = [];
  16097. }
  16098. if (TrackMenuItem === void 0) {
  16099. TrackMenuItem = TextTrackMenuItem;
  16100. }
  16101. // Label is an override for the [track] off label
  16102. // USed to localise captions/subtitles
  16103. var label;
  16104. if (this.label_) {
  16105. label = this.label_ + " off";
  16106. } // Add an OFF menu item to turn all tracks off
  16107. items.push(new OffTextTrackMenuItem(this.player_, {
  16108. kinds: this.kinds_,
  16109. kind: this.kind_,
  16110. label: label
  16111. }));
  16112. this.hideThreshold_ += 1;
  16113. var tracks = this.player_.textTracks();
  16114. if (!Array.isArray(this.kinds_)) {
  16115. this.kinds_ = [this.kind_];
  16116. }
  16117. for (var i = 0; i < tracks.length; i++) {
  16118. var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
  16119. if (this.kinds_.indexOf(track.kind) > -1) {
  16120. var item = new TrackMenuItem(this.player_, {
  16121. track: track,
  16122. kinds: this.kinds_,
  16123. kind: this.kind_,
  16124. // MenuItem is selectable
  16125. selectable: true,
  16126. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  16127. multiSelectable: false
  16128. });
  16129. item.addClass("vjs-" + track.kind + "-menu-item");
  16130. items.push(item);
  16131. }
  16132. }
  16133. return items;
  16134. };
  16135. return TextTrackButton;
  16136. }(TrackButton);
  16137. Component.registerComponent('TextTrackButton', TextTrackButton);
  16138. /**
  16139. * The chapter track menu item
  16140. *
  16141. * @extends MenuItem
  16142. */
  16143. var ChaptersTrackMenuItem =
  16144. /*#__PURE__*/
  16145. function (_MenuItem) {
  16146. _inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
  16147. /**
  16148. * Creates an instance of this class.
  16149. *
  16150. * @param {Player} player
  16151. * The `Player` that this class should be attached to.
  16152. *
  16153. * @param {Object} [options]
  16154. * The key/value store of player options.
  16155. */
  16156. function ChaptersTrackMenuItem(player, options) {
  16157. var _this;
  16158. var track = options.track;
  16159. var cue = options.cue;
  16160. var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
  16161. options.selectable = true;
  16162. options.multiSelectable = false;
  16163. options.label = cue.text;
  16164. options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
  16165. _this = _MenuItem.call(this, player, options) || this;
  16166. _this.track = track;
  16167. _this.cue = cue;
  16168. track.addEventListener('cuechange', bind(_assertThisInitialized(_this), _this.update));
  16169. return _this;
  16170. }
  16171. /**
  16172. * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
  16173. * {@link ClickableComponent} for more detailed information on what a click can be.
  16174. *
  16175. * @param {EventTarget~Event} [event]
  16176. * The `keydown`, `tap`, or `click` event that caused this function to be
  16177. * called.
  16178. *
  16179. * @listens tap
  16180. * @listens click
  16181. */
  16182. var _proto = ChaptersTrackMenuItem.prototype;
  16183. _proto.handleClick = function handleClick(event) {
  16184. _MenuItem.prototype.handleClick.call(this);
  16185. this.player_.currentTime(this.cue.startTime);
  16186. this.update(this.cue.startTime);
  16187. }
  16188. /**
  16189. * Update chapter menu item
  16190. *
  16191. * @param {EventTarget~Event} [event]
  16192. * The `cuechange` event that caused this function to run.
  16193. *
  16194. * @listens TextTrack#cuechange
  16195. */
  16196. ;
  16197. _proto.update = function update(event) {
  16198. var cue = this.cue;
  16199. var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
  16200. this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
  16201. };
  16202. return ChaptersTrackMenuItem;
  16203. }(MenuItem);
  16204. Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
  16205. /**
  16206. * The button component for toggling and selecting chapters
  16207. * Chapters act much differently than other text tracks
  16208. * Cues are navigation vs. other tracks of alternative languages
  16209. *
  16210. * @extends TextTrackButton
  16211. */
  16212. var ChaptersButton =
  16213. /*#__PURE__*/
  16214. function (_TextTrackButton) {
  16215. _inheritsLoose(ChaptersButton, _TextTrackButton);
  16216. /**
  16217. * Creates an instance of this class.
  16218. *
  16219. * @param {Player} player
  16220. * The `Player` that this class should be attached to.
  16221. *
  16222. * @param {Object} [options]
  16223. * The key/value store of player options.
  16224. *
  16225. * @param {Component~ReadyCallback} [ready]
  16226. * The function to call when this function is ready.
  16227. */
  16228. function ChaptersButton(player, options, ready) {
  16229. return _TextTrackButton.call(this, player, options, ready) || this;
  16230. }
  16231. /**
  16232. * Builds the default DOM `className`.
  16233. *
  16234. * @return {string}
  16235. * The DOM `className` for this object.
  16236. */
  16237. var _proto = ChaptersButton.prototype;
  16238. _proto.buildCSSClass = function buildCSSClass() {
  16239. return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  16240. };
  16241. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  16242. return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  16243. }
  16244. /**
  16245. * Update the menu based on the current state of its items.
  16246. *
  16247. * @param {EventTarget~Event} [event]
  16248. * An event that triggered this function to run.
  16249. *
  16250. * @listens TextTrackList#addtrack
  16251. * @listens TextTrackList#removetrack
  16252. * @listens TextTrackList#change
  16253. */
  16254. ;
  16255. _proto.update = function update(event) {
  16256. if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
  16257. this.setTrack(this.findChaptersTrack());
  16258. }
  16259. _TextTrackButton.prototype.update.call(this);
  16260. }
  16261. /**
  16262. * Set the currently selected track for the chapters button.
  16263. *
  16264. * @param {TextTrack} track
  16265. * The new track to select. Nothing will change if this is the currently selected
  16266. * track.
  16267. */
  16268. ;
  16269. _proto.setTrack = function setTrack(track) {
  16270. if (this.track_ === track) {
  16271. return;
  16272. }
  16273. if (!this.updateHandler_) {
  16274. this.updateHandler_ = this.update.bind(this);
  16275. } // here this.track_ refers to the old track instance
  16276. if (this.track_) {
  16277. var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  16278. if (remoteTextTrackEl) {
  16279. remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
  16280. }
  16281. this.track_ = null;
  16282. }
  16283. this.track_ = track; // here this.track_ refers to the new track instance
  16284. if (this.track_) {
  16285. this.track_.mode = 'hidden';
  16286. var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  16287. if (_remoteTextTrackEl) {
  16288. _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
  16289. }
  16290. }
  16291. }
  16292. /**
  16293. * Find the track object that is currently in use by this ChaptersButton
  16294. *
  16295. * @return {TextTrack|undefined}
  16296. * The current track or undefined if none was found.
  16297. */
  16298. ;
  16299. _proto.findChaptersTrack = function findChaptersTrack() {
  16300. var tracks = this.player_.textTracks() || [];
  16301. for (var i = tracks.length - 1; i >= 0; i--) {
  16302. // We will always choose the last track as our chaptersTrack
  16303. var track = tracks[i];
  16304. if (track.kind === this.kind_) {
  16305. return track;
  16306. }
  16307. }
  16308. }
  16309. /**
  16310. * Get the caption for the ChaptersButton based on the track label. This will also
  16311. * use the current tracks localized kind as a fallback if a label does not exist.
  16312. *
  16313. * @return {string}
  16314. * The tracks current label or the localized track kind.
  16315. */
  16316. ;
  16317. _proto.getMenuCaption = function getMenuCaption() {
  16318. if (this.track_ && this.track_.label) {
  16319. return this.track_.label;
  16320. }
  16321. return this.localize(toTitleCase(this.kind_));
  16322. }
  16323. /**
  16324. * Create menu from chapter track
  16325. *
  16326. * @return {Menu}
  16327. * New menu for the chapter buttons
  16328. */
  16329. ;
  16330. _proto.createMenu = function createMenu() {
  16331. this.options_.title = this.getMenuCaption();
  16332. return _TextTrackButton.prototype.createMenu.call(this);
  16333. }
  16334. /**
  16335. * Create a menu item for each text track
  16336. *
  16337. * @return {TextTrackMenuItem[]}
  16338. * Array of menu items
  16339. */
  16340. ;
  16341. _proto.createItems = function createItems() {
  16342. var items = [];
  16343. if (!this.track_) {
  16344. return items;
  16345. }
  16346. var cues = this.track_.cues;
  16347. if (!cues) {
  16348. return items;
  16349. }
  16350. for (var i = 0, l = cues.length; i < l; i++) {
  16351. var cue = cues[i];
  16352. var mi = new ChaptersTrackMenuItem(this.player_, {
  16353. track: this.track_,
  16354. cue: cue
  16355. });
  16356. items.push(mi);
  16357. }
  16358. return items;
  16359. };
  16360. return ChaptersButton;
  16361. }(TextTrackButton);
  16362. /**
  16363. * `kind` of TextTrack to look for to associate it with this menu.
  16364. *
  16365. * @type {string}
  16366. * @private
  16367. */
  16368. ChaptersButton.prototype.kind_ = 'chapters';
  16369. /**
  16370. * The text that should display over the `ChaptersButton`s controls. Added for localization.
  16371. *
  16372. * @type {string}
  16373. * @private
  16374. */
  16375. ChaptersButton.prototype.controlText_ = 'Chapters';
  16376. Component.registerComponent('ChaptersButton', ChaptersButton);
  16377. /**
  16378. * The button component for toggling and selecting descriptions
  16379. *
  16380. * @extends TextTrackButton
  16381. */
  16382. var DescriptionsButton =
  16383. /*#__PURE__*/
  16384. function (_TextTrackButton) {
  16385. _inheritsLoose(DescriptionsButton, _TextTrackButton);
  16386. /**
  16387. * Creates an instance of this class.
  16388. *
  16389. * @param {Player} player
  16390. * The `Player` that this class should be attached to.
  16391. *
  16392. * @param {Object} [options]
  16393. * The key/value store of player options.
  16394. *
  16395. * @param {Component~ReadyCallback} [ready]
  16396. * The function to call when this component is ready.
  16397. */
  16398. function DescriptionsButton(player, options, ready) {
  16399. var _this;
  16400. _this = _TextTrackButton.call(this, player, options, ready) || this;
  16401. var tracks = player.textTracks();
  16402. var changeHandler = bind(_assertThisInitialized(_this), _this.handleTracksChange);
  16403. tracks.addEventListener('change', changeHandler);
  16404. _this.on('dispose', function () {
  16405. tracks.removeEventListener('change', changeHandler);
  16406. });
  16407. return _this;
  16408. }
  16409. /**
  16410. * Handle text track change
  16411. *
  16412. * @param {EventTarget~Event} event
  16413. * The event that caused this function to run
  16414. *
  16415. * @listens TextTrackList#change
  16416. */
  16417. var _proto = DescriptionsButton.prototype;
  16418. _proto.handleTracksChange = function handleTracksChange(event) {
  16419. var tracks = this.player().textTracks();
  16420. var disabled = false; // Check whether a track of a different kind is showing
  16421. for (var i = 0, l = tracks.length; i < l; i++) {
  16422. var track = tracks[i];
  16423. if (track.kind !== this.kind_ && track.mode === 'showing') {
  16424. disabled = true;
  16425. break;
  16426. }
  16427. } // If another track is showing, disable this menu button
  16428. if (disabled) {
  16429. this.disable();
  16430. } else {
  16431. this.enable();
  16432. }
  16433. }
  16434. /**
  16435. * Builds the default DOM `className`.
  16436. *
  16437. * @return {string}
  16438. * The DOM `className` for this object.
  16439. */
  16440. ;
  16441. _proto.buildCSSClass = function buildCSSClass() {
  16442. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  16443. };
  16444. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  16445. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  16446. };
  16447. return DescriptionsButton;
  16448. }(TextTrackButton);
  16449. /**
  16450. * `kind` of TextTrack to look for to associate it with this menu.
  16451. *
  16452. * @type {string}
  16453. * @private
  16454. */
  16455. DescriptionsButton.prototype.kind_ = 'descriptions';
  16456. /**
  16457. * The text that should display over the `DescriptionsButton`s controls. Added for localization.
  16458. *
  16459. * @type {string}
  16460. * @private
  16461. */
  16462. DescriptionsButton.prototype.controlText_ = 'Descriptions';
  16463. Component.registerComponent('DescriptionsButton', DescriptionsButton);
  16464. /**
  16465. * The button component for toggling and selecting subtitles
  16466. *
  16467. * @extends TextTrackButton
  16468. */
  16469. var SubtitlesButton =
  16470. /*#__PURE__*/
  16471. function (_TextTrackButton) {
  16472. _inheritsLoose(SubtitlesButton, _TextTrackButton);
  16473. /**
  16474. * Creates an instance of this class.
  16475. *
  16476. * @param {Player} player
  16477. * The `Player` that this class should be attached to.
  16478. *
  16479. * @param {Object} [options]
  16480. * The key/value store of player options.
  16481. *
  16482. * @param {Component~ReadyCallback} [ready]
  16483. * The function to call when this component is ready.
  16484. */
  16485. function SubtitlesButton(player, options, ready) {
  16486. return _TextTrackButton.call(this, player, options, ready) || this;
  16487. }
  16488. /**
  16489. * Builds the default DOM `className`.
  16490. *
  16491. * @return {string}
  16492. * The DOM `className` for this object.
  16493. */
  16494. var _proto = SubtitlesButton.prototype;
  16495. _proto.buildCSSClass = function buildCSSClass() {
  16496. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  16497. };
  16498. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  16499. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  16500. };
  16501. return SubtitlesButton;
  16502. }(TextTrackButton);
  16503. /**
  16504. * `kind` of TextTrack to look for to associate it with this menu.
  16505. *
  16506. * @type {string}
  16507. * @private
  16508. */
  16509. SubtitlesButton.prototype.kind_ = 'subtitles';
  16510. /**
  16511. * The text that should display over the `SubtitlesButton`s controls. Added for localization.
  16512. *
  16513. * @type {string}
  16514. * @private
  16515. */
  16516. SubtitlesButton.prototype.controlText_ = 'Subtitles';
  16517. Component.registerComponent('SubtitlesButton', SubtitlesButton);
  16518. /**
  16519. * The menu item for caption track settings menu
  16520. *
  16521. * @extends TextTrackMenuItem
  16522. */
  16523. var CaptionSettingsMenuItem =
  16524. /*#__PURE__*/
  16525. function (_TextTrackMenuItem) {
  16526. _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
  16527. /**
  16528. * Creates an instance of this class.
  16529. *
  16530. * @param {Player} player
  16531. * The `Player` that this class should be attached to.
  16532. *
  16533. * @param {Object} [options]
  16534. * The key/value store of player options.
  16535. */
  16536. function CaptionSettingsMenuItem(player, options) {
  16537. var _this;
  16538. options.track = {
  16539. player: player,
  16540. kind: options.kind,
  16541. label: options.kind + ' settings',
  16542. selectable: false,
  16543. "default": false,
  16544. mode: 'disabled'
  16545. }; // CaptionSettingsMenuItem has no concept of 'selected'
  16546. options.selectable = false;
  16547. options.name = 'CaptionSettingsMenuItem';
  16548. _this = _TextTrackMenuItem.call(this, player, options) || this;
  16549. _this.addClass('vjs-texttrack-settings');
  16550. _this.controlText(', opens ' + options.kind + ' settings dialog');
  16551. return _this;
  16552. }
  16553. /**
  16554. * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
  16555. * {@link ClickableComponent} for more detailed information on what a click can be.
  16556. *
  16557. * @param {EventTarget~Event} [event]
  16558. * The `keydown`, `tap`, or `click` event that caused this function to be
  16559. * called.
  16560. *
  16561. * @listens tap
  16562. * @listens click
  16563. */
  16564. var _proto = CaptionSettingsMenuItem.prototype;
  16565. _proto.handleClick = function handleClick(event) {
  16566. this.player().getChild('textTrackSettings').open();
  16567. };
  16568. return CaptionSettingsMenuItem;
  16569. }(TextTrackMenuItem);
  16570. Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
  16571. /**
  16572. * The button component for toggling and selecting captions
  16573. *
  16574. * @extends TextTrackButton
  16575. */
  16576. var CaptionsButton =
  16577. /*#__PURE__*/
  16578. function (_TextTrackButton) {
  16579. _inheritsLoose(CaptionsButton, _TextTrackButton);
  16580. /**
  16581. * Creates an instance of this class.
  16582. *
  16583. * @param {Player} player
  16584. * The `Player` that this class should be attached to.
  16585. *
  16586. * @param {Object} [options]
  16587. * The key/value store of player options.
  16588. *
  16589. * @param {Component~ReadyCallback} [ready]
  16590. * The function to call when this component is ready.
  16591. */
  16592. function CaptionsButton(player, options, ready) {
  16593. return _TextTrackButton.call(this, player, options, ready) || this;
  16594. }
  16595. /**
  16596. * Builds the default DOM `className`.
  16597. *
  16598. * @return {string}
  16599. * The DOM `className` for this object.
  16600. */
  16601. var _proto = CaptionsButton.prototype;
  16602. _proto.buildCSSClass = function buildCSSClass() {
  16603. return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  16604. };
  16605. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  16606. return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  16607. }
  16608. /**
  16609. * Create caption menu items
  16610. *
  16611. * @return {CaptionSettingsMenuItem[]}
  16612. * The array of current menu items.
  16613. */
  16614. ;
  16615. _proto.createItems = function createItems() {
  16616. var items = [];
  16617. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  16618. items.push(new CaptionSettingsMenuItem(this.player_, {
  16619. kind: this.kind_
  16620. }));
  16621. this.hideThreshold_ += 1;
  16622. }
  16623. return _TextTrackButton.prototype.createItems.call(this, items);
  16624. };
  16625. return CaptionsButton;
  16626. }(TextTrackButton);
  16627. /**
  16628. * `kind` of TextTrack to look for to associate it with this menu.
  16629. *
  16630. * @type {string}
  16631. * @private
  16632. */
  16633. CaptionsButton.prototype.kind_ = 'captions';
  16634. /**
  16635. * The text that should display over the `CaptionsButton`s controls. Added for localization.
  16636. *
  16637. * @type {string}
  16638. * @private
  16639. */
  16640. CaptionsButton.prototype.controlText_ = 'Captions';
  16641. Component.registerComponent('CaptionsButton', CaptionsButton);
  16642. /**
  16643. * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
  16644. * in the SubsCapsMenu.
  16645. *
  16646. * @extends TextTrackMenuItem
  16647. */
  16648. var SubsCapsMenuItem =
  16649. /*#__PURE__*/
  16650. function (_TextTrackMenuItem) {
  16651. _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
  16652. function SubsCapsMenuItem() {
  16653. return _TextTrackMenuItem.apply(this, arguments) || this;
  16654. }
  16655. var _proto = SubsCapsMenuItem.prototype;
  16656. _proto.createEl = function createEl(type, props, attrs) {
  16657. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  16658. if (this.options_.track.kind === 'captions') {
  16659. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n ";
  16660. }
  16661. innerHTML += '</span>';
  16662. var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
  16663. innerHTML: innerHTML
  16664. }, props), attrs);
  16665. return el;
  16666. };
  16667. return SubsCapsMenuItem;
  16668. }(TextTrackMenuItem);
  16669. Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
  16670. /**
  16671. * The button component for toggling and selecting captions and/or subtitles
  16672. *
  16673. * @extends TextTrackButton
  16674. */
  16675. var SubsCapsButton =
  16676. /*#__PURE__*/
  16677. function (_TextTrackButton) {
  16678. _inheritsLoose(SubsCapsButton, _TextTrackButton);
  16679. function SubsCapsButton(player, options) {
  16680. var _this;
  16681. if (options === void 0) {
  16682. options = {};
  16683. }
  16684. _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
  16685. // "captions and subtitles" other locales use "subtitles"
  16686. _this.label_ = 'subtitles';
  16687. if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
  16688. _this.label_ = 'captions';
  16689. }
  16690. _this.menuButton_.controlText(toTitleCase(_this.label_));
  16691. return _this;
  16692. }
  16693. /**
  16694. * Builds the default DOM `className`.
  16695. *
  16696. * @return {string}
  16697. * The DOM `className` for this object.
  16698. */
  16699. var _proto = SubsCapsButton.prototype;
  16700. _proto.buildCSSClass = function buildCSSClass() {
  16701. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  16702. };
  16703. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  16704. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  16705. }
  16706. /**
  16707. * Create caption/subtitles menu items
  16708. *
  16709. * @return {CaptionSettingsMenuItem[]}
  16710. * The array of current menu items.
  16711. */
  16712. ;
  16713. _proto.createItems = function createItems() {
  16714. var items = [];
  16715. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  16716. items.push(new CaptionSettingsMenuItem(this.player_, {
  16717. kind: this.label_
  16718. }));
  16719. this.hideThreshold_ += 1;
  16720. }
  16721. items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
  16722. return items;
  16723. };
  16724. return SubsCapsButton;
  16725. }(TextTrackButton);
  16726. /**
  16727. * `kind`s of TextTrack to look for to associate it with this menu.
  16728. *
  16729. * @type {array}
  16730. * @private
  16731. */
  16732. SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
  16733. /**
  16734. * The text that should display over the `SubsCapsButton`s controls.
  16735. *
  16736. *
  16737. * @type {string}
  16738. * @private
  16739. */
  16740. SubsCapsButton.prototype.controlText_ = 'Subtitles';
  16741. Component.registerComponent('SubsCapsButton', SubsCapsButton);
  16742. /**
  16743. * An {@link AudioTrack} {@link MenuItem}
  16744. *
  16745. * @extends MenuItem
  16746. */
  16747. var AudioTrackMenuItem =
  16748. /*#__PURE__*/
  16749. function (_MenuItem) {
  16750. _inheritsLoose(AudioTrackMenuItem, _MenuItem);
  16751. /**
  16752. * Creates an instance of this class.
  16753. *
  16754. * @param {Player} player
  16755. * The `Player` that this class should be attached to.
  16756. *
  16757. * @param {Object} [options]
  16758. * The key/value store of player options.
  16759. */
  16760. function AudioTrackMenuItem(player, options) {
  16761. var _this;
  16762. var track = options.track;
  16763. var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
  16764. options.label = track.label || track.language || 'Unknown';
  16765. options.selected = track.enabled;
  16766. _this = _MenuItem.call(this, player, options) || this;
  16767. _this.track = track;
  16768. _this.addClass("vjs-" + track.kind + "-menu-item");
  16769. var changeHandler = function changeHandler() {
  16770. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  16771. args[_key] = arguments[_key];
  16772. }
  16773. _this.handleTracksChange.apply(_assertThisInitialized(_this), args);
  16774. };
  16775. tracks.addEventListener('change', changeHandler);
  16776. _this.on('dispose', function () {
  16777. tracks.removeEventListener('change', changeHandler);
  16778. });
  16779. return _this;
  16780. }
  16781. var _proto = AudioTrackMenuItem.prototype;
  16782. _proto.createEl = function createEl(type, props, attrs) {
  16783. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  16784. if (this.options_.track.kind === 'main-desc') {
  16785. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n ";
  16786. }
  16787. innerHTML += '</span>';
  16788. var el = _MenuItem.prototype.createEl.call(this, type, assign({
  16789. innerHTML: innerHTML
  16790. }, props), attrs);
  16791. return el;
  16792. }
  16793. /**
  16794. * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
  16795. * for more detailed information on what a click can be.
  16796. *
  16797. * @param {EventTarget~Event} [event]
  16798. * The `keydown`, `tap`, or `click` event that caused this function to be
  16799. * called.
  16800. *
  16801. * @listens tap
  16802. * @listens click
  16803. */
  16804. ;
  16805. _proto.handleClick = function handleClick(event) {
  16806. var tracks = this.player_.audioTracks();
  16807. _MenuItem.prototype.handleClick.call(this, event);
  16808. for (var i = 0; i < tracks.length; i++) {
  16809. var track = tracks[i];
  16810. track.enabled = track === this.track;
  16811. }
  16812. }
  16813. /**
  16814. * Handle any {@link AudioTrack} change.
  16815. *
  16816. * @param {EventTarget~Event} [event]
  16817. * The {@link AudioTrackList#change} event that caused this to run.
  16818. *
  16819. * @listens AudioTrackList#change
  16820. */
  16821. ;
  16822. _proto.handleTracksChange = function handleTracksChange(event) {
  16823. this.selected(this.track.enabled);
  16824. };
  16825. return AudioTrackMenuItem;
  16826. }(MenuItem);
  16827. Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
  16828. /**
  16829. * The base class for buttons that toggle specific {@link AudioTrack} types.
  16830. *
  16831. * @extends TrackButton
  16832. */
  16833. var AudioTrackButton =
  16834. /*#__PURE__*/
  16835. function (_TrackButton) {
  16836. _inheritsLoose(AudioTrackButton, _TrackButton);
  16837. /**
  16838. * Creates an instance of this class.
  16839. *
  16840. * @param {Player} player
  16841. * The `Player` that this class should be attached to.
  16842. *
  16843. * @param {Object} [options={}]
  16844. * The key/value store of player options.
  16845. */
  16846. function AudioTrackButton(player, options) {
  16847. if (options === void 0) {
  16848. options = {};
  16849. }
  16850. options.tracks = player.audioTracks();
  16851. return _TrackButton.call(this, player, options) || this;
  16852. }
  16853. /**
  16854. * Builds the default DOM `className`.
  16855. *
  16856. * @return {string}
  16857. * The DOM `className` for this object.
  16858. */
  16859. var _proto = AudioTrackButton.prototype;
  16860. _proto.buildCSSClass = function buildCSSClass() {
  16861. return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
  16862. };
  16863. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  16864. return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
  16865. }
  16866. /**
  16867. * Create a menu item for each audio track
  16868. *
  16869. * @param {AudioTrackMenuItem[]} [items=[]]
  16870. * An array of existing menu items to use.
  16871. *
  16872. * @return {AudioTrackMenuItem[]}
  16873. * An array of menu items
  16874. */
  16875. ;
  16876. _proto.createItems = function createItems(items) {
  16877. if (items === void 0) {
  16878. items = [];
  16879. }
  16880. // if there's only one audio track, there no point in showing it
  16881. this.hideThreshold_ = 1;
  16882. var tracks = this.player_.audioTracks();
  16883. for (var i = 0; i < tracks.length; i++) {
  16884. var track = tracks[i];
  16885. items.push(new AudioTrackMenuItem(this.player_, {
  16886. track: track,
  16887. // MenuItem is selectable
  16888. selectable: true,
  16889. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  16890. multiSelectable: false
  16891. }));
  16892. }
  16893. return items;
  16894. };
  16895. return AudioTrackButton;
  16896. }(TrackButton);
  16897. /**
  16898. * The text that should display over the `AudioTrackButton`s controls. Added for localization.
  16899. *
  16900. * @type {string}
  16901. * @private
  16902. */
  16903. AudioTrackButton.prototype.controlText_ = 'Audio Track';
  16904. Component.registerComponent('AudioTrackButton', AudioTrackButton);
  16905. /**
  16906. * The specific menu item type for selecting a playback rate.
  16907. *
  16908. * @extends MenuItem
  16909. */
  16910. var PlaybackRateMenuItem =
  16911. /*#__PURE__*/
  16912. function (_MenuItem) {
  16913. _inheritsLoose(PlaybackRateMenuItem, _MenuItem);
  16914. /**
  16915. * Creates an instance of this class.
  16916. *
  16917. * @param {Player} player
  16918. * The `Player` that this class should be attached to.
  16919. *
  16920. * @param {Object} [options]
  16921. * The key/value store of player options.
  16922. */
  16923. function PlaybackRateMenuItem(player, options) {
  16924. var _this;
  16925. var label = options.rate;
  16926. var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
  16927. options.label = label;
  16928. options.selected = rate === 1;
  16929. options.selectable = true;
  16930. options.multiSelectable = false;
  16931. _this = _MenuItem.call(this, player, options) || this;
  16932. _this.label = label;
  16933. _this.rate = rate;
  16934. _this.on(player, 'ratechange', _this.update);
  16935. return _this;
  16936. }
  16937. /**
  16938. * This gets called when an `PlaybackRateMenuItem` is "clicked". See
  16939. * {@link ClickableComponent} for more detailed information on what a click can be.
  16940. *
  16941. * @param {EventTarget~Event} [event]
  16942. * The `keydown`, `tap`, or `click` event that caused this function to be
  16943. * called.
  16944. *
  16945. * @listens tap
  16946. * @listens click
  16947. */
  16948. var _proto = PlaybackRateMenuItem.prototype;
  16949. _proto.handleClick = function handleClick(event) {
  16950. _MenuItem.prototype.handleClick.call(this);
  16951. this.player().playbackRate(this.rate);
  16952. }
  16953. /**
  16954. * Update the PlaybackRateMenuItem when the playbackrate changes.
  16955. *
  16956. * @param {EventTarget~Event} [event]
  16957. * The `ratechange` event that caused this function to run.
  16958. *
  16959. * @listens Player#ratechange
  16960. */
  16961. ;
  16962. _proto.update = function update(event) {
  16963. this.selected(this.player().playbackRate() === this.rate);
  16964. };
  16965. return PlaybackRateMenuItem;
  16966. }(MenuItem);
  16967. /**
  16968. * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
  16969. *
  16970. * @type {string}
  16971. * @private
  16972. */
  16973. PlaybackRateMenuItem.prototype.contentElType = 'button';
  16974. Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
  16975. /**
  16976. * The component for controlling the playback rate.
  16977. *
  16978. * @extends MenuButton
  16979. */
  16980. var PlaybackRateMenuButton =
  16981. /*#__PURE__*/
  16982. function (_MenuButton) {
  16983. _inheritsLoose(PlaybackRateMenuButton, _MenuButton);
  16984. /**
  16985. * Creates an instance of this class.
  16986. *
  16987. * @param {Player} player
  16988. * The `Player` that this class should be attached to.
  16989. *
  16990. * @param {Object} [options]
  16991. * The key/value store of player options.
  16992. */
  16993. function PlaybackRateMenuButton(player, options) {
  16994. var _this;
  16995. _this = _MenuButton.call(this, player, options) || this;
  16996. _this.updateVisibility();
  16997. _this.updateLabel();
  16998. _this.on(player, 'loadstart', _this.updateVisibility);
  16999. _this.on(player, 'ratechange', _this.updateLabel);
  17000. return _this;
  17001. }
  17002. /**
  17003. * Create the `Component`'s DOM element
  17004. *
  17005. * @return {Element}
  17006. * The element that was created.
  17007. */
  17008. var _proto = PlaybackRateMenuButton.prototype;
  17009. _proto.createEl = function createEl$1() {
  17010. var el = _MenuButton.prototype.createEl.call(this);
  17011. this.labelEl_ = createEl('div', {
  17012. className: 'vjs-playback-rate-value',
  17013. innerHTML: '1x'
  17014. });
  17015. el.appendChild(this.labelEl_);
  17016. return el;
  17017. };
  17018. _proto.dispose = function dispose() {
  17019. this.labelEl_ = null;
  17020. _MenuButton.prototype.dispose.call(this);
  17021. }
  17022. /**
  17023. * Builds the default DOM `className`.
  17024. *
  17025. * @return {string}
  17026. * The DOM `className` for this object.
  17027. */
  17028. ;
  17029. _proto.buildCSSClass = function buildCSSClass() {
  17030. return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
  17031. };
  17032. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  17033. return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
  17034. }
  17035. /**
  17036. * Create the playback rate menu
  17037. *
  17038. * @return {Menu}
  17039. * Menu object populated with {@link PlaybackRateMenuItem}s
  17040. */
  17041. ;
  17042. _proto.createMenu = function createMenu() {
  17043. var menu = new Menu(this.player());
  17044. var rates = this.playbackRates();
  17045. if (rates) {
  17046. for (var i = rates.length - 1; i >= 0; i--) {
  17047. menu.addChild(new PlaybackRateMenuItem(this.player(), {
  17048. rate: rates[i] + 'x'
  17049. }));
  17050. }
  17051. }
  17052. return menu;
  17053. }
  17054. /**
  17055. * Updates ARIA accessibility attributes
  17056. */
  17057. ;
  17058. _proto.updateARIAAttributes = function updateARIAAttributes() {
  17059. // Current playback rate
  17060. this.el().setAttribute('aria-valuenow', this.player().playbackRate());
  17061. }
  17062. /**
  17063. * This gets called when an `PlaybackRateMenuButton` is "clicked". See
  17064. * {@link ClickableComponent} for more detailed information on what a click can be.
  17065. *
  17066. * @param {EventTarget~Event} [event]
  17067. * The `keydown`, `tap`, or `click` event that caused this function to be
  17068. * called.
  17069. *
  17070. * @listens tap
  17071. * @listens click
  17072. */
  17073. ;
  17074. _proto.handleClick = function handleClick(event) {
  17075. // select next rate option
  17076. var currentRate = this.player().playbackRate();
  17077. var rates = this.playbackRates(); // this will select first one if the last one currently selected
  17078. var newRate = rates[0];
  17079. for (var i = 0; i < rates.length; i++) {
  17080. if (rates[i] > currentRate) {
  17081. newRate = rates[i];
  17082. break;
  17083. }
  17084. }
  17085. this.player().playbackRate(newRate);
  17086. }
  17087. /**
  17088. * Get possible playback rates
  17089. *
  17090. * @return {Array}
  17091. * All possible playback rates
  17092. */
  17093. ;
  17094. _proto.playbackRates = function playbackRates() {
  17095. return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
  17096. }
  17097. /**
  17098. * Get whether playback rates is supported by the tech
  17099. * and an array of playback rates exists
  17100. *
  17101. * @return {boolean}
  17102. * Whether changing playback rate is supported
  17103. */
  17104. ;
  17105. _proto.playbackRateSupported = function playbackRateSupported() {
  17106. return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
  17107. }
  17108. /**
  17109. * Hide playback rate controls when they're no playback rate options to select
  17110. *
  17111. * @param {EventTarget~Event} [event]
  17112. * The event that caused this function to run.
  17113. *
  17114. * @listens Player#loadstart
  17115. */
  17116. ;
  17117. _proto.updateVisibility = function updateVisibility(event) {
  17118. if (this.playbackRateSupported()) {
  17119. this.removeClass('vjs-hidden');
  17120. } else {
  17121. this.addClass('vjs-hidden');
  17122. }
  17123. }
  17124. /**
  17125. * Update button label when rate changed
  17126. *
  17127. * @param {EventTarget~Event} [event]
  17128. * The event that caused this function to run.
  17129. *
  17130. * @listens Player#ratechange
  17131. */
  17132. ;
  17133. _proto.updateLabel = function updateLabel(event) {
  17134. if (this.playbackRateSupported()) {
  17135. this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
  17136. }
  17137. };
  17138. return PlaybackRateMenuButton;
  17139. }(MenuButton);
  17140. /**
  17141. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  17142. *
  17143. * @type {string}
  17144. * @private
  17145. */
  17146. PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
  17147. Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
  17148. /**
  17149. * Just an empty spacer element that can be used as an append point for plugins, etc.
  17150. * Also can be used to create space between elements when necessary.
  17151. *
  17152. * @extends Component
  17153. */
  17154. var Spacer =
  17155. /*#__PURE__*/
  17156. function (_Component) {
  17157. _inheritsLoose(Spacer, _Component);
  17158. function Spacer() {
  17159. return _Component.apply(this, arguments) || this;
  17160. }
  17161. var _proto = Spacer.prototype;
  17162. /**
  17163. * Builds the default DOM `className`.
  17164. *
  17165. * @return {string}
  17166. * The DOM `className` for this object.
  17167. */
  17168. _proto.buildCSSClass = function buildCSSClass() {
  17169. return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
  17170. }
  17171. /**
  17172. * Create the `Component`'s DOM element
  17173. *
  17174. * @return {Element}
  17175. * The element that was created.
  17176. */
  17177. ;
  17178. _proto.createEl = function createEl() {
  17179. return _Component.prototype.createEl.call(this, 'div', {
  17180. className: this.buildCSSClass()
  17181. });
  17182. };
  17183. return Spacer;
  17184. }(Component);
  17185. Component.registerComponent('Spacer', Spacer);
  17186. /**
  17187. * Spacer specifically meant to be used as an insertion point for new plugins, etc.
  17188. *
  17189. * @extends Spacer
  17190. */
  17191. var CustomControlSpacer =
  17192. /*#__PURE__*/
  17193. function (_Spacer) {
  17194. _inheritsLoose(CustomControlSpacer, _Spacer);
  17195. function CustomControlSpacer() {
  17196. return _Spacer.apply(this, arguments) || this;
  17197. }
  17198. var _proto = CustomControlSpacer.prototype;
  17199. /**
  17200. * Builds the default DOM `className`.
  17201. *
  17202. * @return {string}
  17203. * The DOM `className` for this object.
  17204. */
  17205. _proto.buildCSSClass = function buildCSSClass() {
  17206. return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
  17207. }
  17208. /**
  17209. * Create the `Component`'s DOM element
  17210. *
  17211. * @return {Element}
  17212. * The element that was created.
  17213. */
  17214. ;
  17215. _proto.createEl = function createEl() {
  17216. var el = _Spacer.prototype.createEl.call(this, {
  17217. className: this.buildCSSClass()
  17218. }); // No-flex/table-cell mode requires there be some content
  17219. // in the cell to fill the remaining space of the table.
  17220. el.innerHTML = "\xA0";
  17221. return el;
  17222. };
  17223. return CustomControlSpacer;
  17224. }(Spacer);
  17225. Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
  17226. /**
  17227. * Container of main controls.
  17228. *
  17229. * @extends Component
  17230. */
  17231. var ControlBar =
  17232. /*#__PURE__*/
  17233. function (_Component) {
  17234. _inheritsLoose(ControlBar, _Component);
  17235. function ControlBar() {
  17236. return _Component.apply(this, arguments) || this;
  17237. }
  17238. var _proto = ControlBar.prototype;
  17239. /**
  17240. * Create the `Component`'s DOM element
  17241. *
  17242. * @return {Element}
  17243. * The element that was created.
  17244. */
  17245. _proto.createEl = function createEl() {
  17246. return _Component.prototype.createEl.call(this, 'div', {
  17247. className: 'vjs-control-bar',
  17248. dir: 'ltr'
  17249. });
  17250. };
  17251. return ControlBar;
  17252. }(Component);
  17253. /**
  17254. * Default options for `ControlBar`
  17255. *
  17256. * @type {Object}
  17257. * @private
  17258. */
  17259. ControlBar.prototype.options_ = {
  17260. children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'pictureInPictureToggle', 'fullscreenToggle']
  17261. };
  17262. Component.registerComponent('ControlBar', ControlBar);
  17263. /**
  17264. * A display that indicates an error has occurred. This means that the video
  17265. * is unplayable.
  17266. *
  17267. * @extends ModalDialog
  17268. */
  17269. var ErrorDisplay =
  17270. /*#__PURE__*/
  17271. function (_ModalDialog) {
  17272. _inheritsLoose(ErrorDisplay, _ModalDialog);
  17273. /**
  17274. * Creates an instance of this class.
  17275. *
  17276. * @param {Player} player
  17277. * The `Player` that this class should be attached to.
  17278. *
  17279. * @param {Object} [options]
  17280. * The key/value store of player options.
  17281. */
  17282. function ErrorDisplay(player, options) {
  17283. var _this;
  17284. _this = _ModalDialog.call(this, player, options) || this;
  17285. _this.on(player, 'error', _this.open);
  17286. return _this;
  17287. }
  17288. /**
  17289. * Builds the default DOM `className`.
  17290. *
  17291. * @return {string}
  17292. * The DOM `className` for this object.
  17293. *
  17294. * @deprecated Since version 5.
  17295. */
  17296. var _proto = ErrorDisplay.prototype;
  17297. _proto.buildCSSClass = function buildCSSClass() {
  17298. return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
  17299. }
  17300. /**
  17301. * Gets the localized error message based on the `Player`s error.
  17302. *
  17303. * @return {string}
  17304. * The `Player`s error message localized or an empty string.
  17305. */
  17306. ;
  17307. _proto.content = function content() {
  17308. var error = this.player().error();
  17309. return error ? this.localize(error.message) : '';
  17310. };
  17311. return ErrorDisplay;
  17312. }(ModalDialog);
  17313. /**
  17314. * The default options for an `ErrorDisplay`.
  17315. *
  17316. * @private
  17317. */
  17318. ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
  17319. pauseOnOpen: false,
  17320. fillAlways: true,
  17321. temporary: false,
  17322. uncloseable: true
  17323. });
  17324. Component.registerComponent('ErrorDisplay', ErrorDisplay);
  17325. var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
  17326. var COLOR_BLACK = ['#000', 'Black'];
  17327. var COLOR_BLUE = ['#00F', 'Blue'];
  17328. var COLOR_CYAN = ['#0FF', 'Cyan'];
  17329. var COLOR_GREEN = ['#0F0', 'Green'];
  17330. var COLOR_MAGENTA = ['#F0F', 'Magenta'];
  17331. var COLOR_RED = ['#F00', 'Red'];
  17332. var COLOR_WHITE = ['#FFF', 'White'];
  17333. var COLOR_YELLOW = ['#FF0', 'Yellow'];
  17334. var OPACITY_OPAQUE = ['1', 'Opaque'];
  17335. var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
  17336. var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
  17337. //
  17338. // Possible keys include:
  17339. //
  17340. // `default`:
  17341. // The default option index. Only needs to be provided if not zero.
  17342. // `parser`:
  17343. // A function which is used to parse the value from the selected option in
  17344. // a customized way.
  17345. // `selector`:
  17346. // The selector used to find the associated <select> element.
  17347. var selectConfigs = {
  17348. backgroundColor: {
  17349. selector: '.vjs-bg-color > select',
  17350. id: 'captions-background-color-%s',
  17351. label: 'Color',
  17352. options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  17353. },
  17354. backgroundOpacity: {
  17355. selector: '.vjs-bg-opacity > select',
  17356. id: 'captions-background-opacity-%s',
  17357. label: 'Transparency',
  17358. options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
  17359. },
  17360. color: {
  17361. selector: '.vjs-fg-color > select',
  17362. id: 'captions-foreground-color-%s',
  17363. label: 'Color',
  17364. options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  17365. },
  17366. edgeStyle: {
  17367. selector: '.vjs-edge-style > select',
  17368. id: '%s',
  17369. label: 'Text Edge Style',
  17370. options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
  17371. },
  17372. fontFamily: {
  17373. selector: '.vjs-font-family > select',
  17374. id: 'captions-font-family-%s',
  17375. label: 'Font Family',
  17376. options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
  17377. },
  17378. fontPercent: {
  17379. selector: '.vjs-font-percent > select',
  17380. id: 'captions-font-size-%s',
  17381. label: 'Font Size',
  17382. 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%']],
  17383. "default": 2,
  17384. parser: function parser(v) {
  17385. return v === '1.00' ? null : Number(v);
  17386. }
  17387. },
  17388. textOpacity: {
  17389. selector: '.vjs-text-opacity > select',
  17390. id: 'captions-foreground-opacity-%s',
  17391. label: 'Transparency',
  17392. options: [OPACITY_OPAQUE, OPACITY_SEMI]
  17393. },
  17394. // Options for this object are defined below.
  17395. windowColor: {
  17396. selector: '.vjs-window-color > select',
  17397. id: 'captions-window-color-%s',
  17398. label: 'Color'
  17399. },
  17400. // Options for this object are defined below.
  17401. windowOpacity: {
  17402. selector: '.vjs-window-opacity > select',
  17403. id: 'captions-window-opacity-%s',
  17404. label: 'Transparency',
  17405. options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
  17406. }
  17407. };
  17408. selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
  17409. /**
  17410. * Get the actual value of an option.
  17411. *
  17412. * @param {string} value
  17413. * The value to get
  17414. *
  17415. * @param {Function} [parser]
  17416. * Optional function to adjust the value.
  17417. *
  17418. * @return {Mixed}
  17419. * - Will be `undefined` if no value exists
  17420. * - Will be `undefined` if the given value is "none".
  17421. * - Will be the actual value otherwise.
  17422. *
  17423. * @private
  17424. */
  17425. function parseOptionValue(value, parser) {
  17426. if (parser) {
  17427. value = parser(value);
  17428. }
  17429. if (value && value !== 'none') {
  17430. return value;
  17431. }
  17432. }
  17433. /**
  17434. * Gets the value of the selected <option> element within a <select> element.
  17435. *
  17436. * @param {Element} el
  17437. * the element to look in
  17438. *
  17439. * @param {Function} [parser]
  17440. * Optional function to adjust the value.
  17441. *
  17442. * @return {Mixed}
  17443. * - Will be `undefined` if no value exists
  17444. * - Will be `undefined` if the given value is "none".
  17445. * - Will be the actual value otherwise.
  17446. *
  17447. * @private
  17448. */
  17449. function getSelectedOptionValue(el, parser) {
  17450. var value = el.options[el.options.selectedIndex].value;
  17451. return parseOptionValue(value, parser);
  17452. }
  17453. /**
  17454. * Sets the selected <option> element within a <select> element based on a
  17455. * given value.
  17456. *
  17457. * @param {Element} el
  17458. * The element to look in.
  17459. *
  17460. * @param {string} value
  17461. * the property to look on.
  17462. *
  17463. * @param {Function} [parser]
  17464. * Optional function to adjust the value before comparing.
  17465. *
  17466. * @private
  17467. */
  17468. function setSelectedOption(el, value, parser) {
  17469. if (!value) {
  17470. return;
  17471. }
  17472. for (var i = 0; i < el.options.length; i++) {
  17473. if (parseOptionValue(el.options[i].value, parser) === value) {
  17474. el.selectedIndex = i;
  17475. break;
  17476. }
  17477. }
  17478. }
  17479. /**
  17480. * Manipulate Text Tracks settings.
  17481. *
  17482. * @extends ModalDialog
  17483. */
  17484. var TextTrackSettings =
  17485. /*#__PURE__*/
  17486. function (_ModalDialog) {
  17487. _inheritsLoose(TextTrackSettings, _ModalDialog);
  17488. /**
  17489. * Creates an instance of this class.
  17490. *
  17491. * @param {Player} player
  17492. * The `Player` that this class should be attached to.
  17493. *
  17494. * @param {Object} [options]
  17495. * The key/value store of player options.
  17496. */
  17497. function TextTrackSettings(player, options) {
  17498. var _this;
  17499. options.temporary = false;
  17500. _this = _ModalDialog.call(this, player, options) || this;
  17501. _this.updateDisplay = bind(_assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it
  17502. _this.fill();
  17503. _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
  17504. _this.endDialog = createEl('p', {
  17505. className: 'vjs-control-text',
  17506. textContent: _this.localize('End of dialog window.')
  17507. });
  17508. _this.el().appendChild(_this.endDialog);
  17509. _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
  17510. if (options.persistTextTrackSettings === undefined) {
  17511. _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
  17512. }
  17513. _this.on(_this.$('.vjs-done-button'), 'click', function () {
  17514. _this.saveSettings();
  17515. _this.close();
  17516. });
  17517. _this.on(_this.$('.vjs-default-button'), 'click', function () {
  17518. _this.setDefaults();
  17519. _this.updateDisplay();
  17520. });
  17521. each(selectConfigs, function (config) {
  17522. _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
  17523. });
  17524. if (_this.options_.persistTextTrackSettings) {
  17525. _this.restoreSettings();
  17526. }
  17527. return _this;
  17528. }
  17529. var _proto = TextTrackSettings.prototype;
  17530. _proto.dispose = function dispose() {
  17531. this.endDialog = null;
  17532. _ModalDialog.prototype.dispose.call(this);
  17533. }
  17534. /**
  17535. * Create a <select> element with configured options.
  17536. *
  17537. * @param {string} key
  17538. * Configuration key to use during creation.
  17539. *
  17540. * @return {string}
  17541. * An HTML string.
  17542. *
  17543. * @private
  17544. */
  17545. ;
  17546. _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
  17547. var _this2 = this;
  17548. if (legendId === void 0) {
  17549. legendId = '';
  17550. }
  17551. if (type === void 0) {
  17552. type = 'label';
  17553. }
  17554. var config = selectConfigs[key];
  17555. var id = config.id.replace('%s', this.id_);
  17556. var selectLabelledbyIds = [legendId, id].join(' ').trim();
  17557. return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
  17558. var optionId = id + '-' + o[1].replace(/\W+/g, '');
  17559. return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
  17560. })).concat('</select>').join('');
  17561. }
  17562. /**
  17563. * Create foreground color element for the component
  17564. *
  17565. * @return {string}
  17566. * An HTML string.
  17567. *
  17568. * @private
  17569. */
  17570. ;
  17571. _proto.createElFgColor_ = function createElFgColor_() {
  17572. var legendId = "captions-text-legend-" + this.id_;
  17573. 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('');
  17574. }
  17575. /**
  17576. * Create background color element for the component
  17577. *
  17578. * @return {string}
  17579. * An HTML string.
  17580. *
  17581. * @private
  17582. */
  17583. ;
  17584. _proto.createElBgColor_ = function createElBgColor_() {
  17585. var legendId = "captions-background-" + this.id_;
  17586. 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('');
  17587. }
  17588. /**
  17589. * Create window color element for the component
  17590. *
  17591. * @return {string}
  17592. * An HTML string.
  17593. *
  17594. * @private
  17595. */
  17596. ;
  17597. _proto.createElWinColor_ = function createElWinColor_() {
  17598. var legendId = "captions-window-" + this.id_;
  17599. 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('');
  17600. }
  17601. /**
  17602. * Create color elements for the component
  17603. *
  17604. * @return {Element}
  17605. * The element that was created
  17606. *
  17607. * @private
  17608. */
  17609. ;
  17610. _proto.createElColors_ = function createElColors_() {
  17611. return createEl('div', {
  17612. className: 'vjs-track-settings-colors',
  17613. innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
  17614. });
  17615. }
  17616. /**
  17617. * Create font elements for the component
  17618. *
  17619. * @return {Element}
  17620. * The element that was created.
  17621. *
  17622. * @private
  17623. */
  17624. ;
  17625. _proto.createElFont_ = function createElFont_() {
  17626. return createEl('div', {
  17627. className: 'vjs-track-settings-font',
  17628. 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('')
  17629. });
  17630. }
  17631. /**
  17632. * Create controls for the component
  17633. *
  17634. * @return {Element}
  17635. * The element that was created.
  17636. *
  17637. * @private
  17638. */
  17639. ;
  17640. _proto.createElControls_ = function createElControls_() {
  17641. var defaultsDescription = this.localize('restore all settings to the default values');
  17642. return createEl('div', {
  17643. className: 'vjs-track-settings-controls',
  17644. 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('')
  17645. });
  17646. };
  17647. _proto.content = function content() {
  17648. return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
  17649. };
  17650. _proto.label = function label() {
  17651. return this.localize('Caption Settings Dialog');
  17652. };
  17653. _proto.description = function description() {
  17654. return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
  17655. };
  17656. _proto.buildCSSClass = function buildCSSClass() {
  17657. return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
  17658. }
  17659. /**
  17660. * Gets an object of text track settings (or null).
  17661. *
  17662. * @return {Object}
  17663. * An object with config values parsed from the DOM or localStorage.
  17664. */
  17665. ;
  17666. _proto.getValues = function getValues() {
  17667. var _this3 = this;
  17668. return reduce(selectConfigs, function (accum, config, key) {
  17669. var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
  17670. if (value !== undefined) {
  17671. accum[key] = value;
  17672. }
  17673. return accum;
  17674. }, {});
  17675. }
  17676. /**
  17677. * Sets text track settings from an object of values.
  17678. *
  17679. * @param {Object} values
  17680. * An object with config values parsed from the DOM or localStorage.
  17681. */
  17682. ;
  17683. _proto.setValues = function setValues(values) {
  17684. var _this4 = this;
  17685. each(selectConfigs, function (config, key) {
  17686. setSelectedOption(_this4.$(config.selector), values[key], config.parser);
  17687. });
  17688. }
  17689. /**
  17690. * Sets all `<select>` elements to their default values.
  17691. */
  17692. ;
  17693. _proto.setDefaults = function setDefaults() {
  17694. var _this5 = this;
  17695. each(selectConfigs, function (config) {
  17696. var index = config.hasOwnProperty('default') ? config["default"] : 0;
  17697. _this5.$(config.selector).selectedIndex = index;
  17698. });
  17699. }
  17700. /**
  17701. * Restore texttrack settings from localStorage
  17702. */
  17703. ;
  17704. _proto.restoreSettings = function restoreSettings() {
  17705. var values;
  17706. try {
  17707. values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
  17708. } catch (err) {
  17709. log.warn(err);
  17710. }
  17711. if (values) {
  17712. this.setValues(values);
  17713. }
  17714. }
  17715. /**
  17716. * Save text track settings to localStorage
  17717. */
  17718. ;
  17719. _proto.saveSettings = function saveSettings() {
  17720. if (!this.options_.persistTextTrackSettings) {
  17721. return;
  17722. }
  17723. var values = this.getValues();
  17724. try {
  17725. if (Object.keys(values).length) {
  17726. window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
  17727. } else {
  17728. window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
  17729. }
  17730. } catch (err) {
  17731. log.warn(err);
  17732. }
  17733. }
  17734. /**
  17735. * Update display of text track settings
  17736. */
  17737. ;
  17738. _proto.updateDisplay = function updateDisplay() {
  17739. var ttDisplay = this.player_.getChild('textTrackDisplay');
  17740. if (ttDisplay) {
  17741. ttDisplay.updateDisplay();
  17742. }
  17743. }
  17744. /**
  17745. * conditionally blur the element and refocus the captions button
  17746. *
  17747. * @private
  17748. */
  17749. ;
  17750. _proto.conditionalBlur_ = function conditionalBlur_() {
  17751. this.previouslyActiveEl_ = null;
  17752. var cb = this.player_.controlBar;
  17753. var subsCapsBtn = cb && cb.subsCapsButton;
  17754. var ccBtn = cb && cb.captionsButton;
  17755. if (subsCapsBtn) {
  17756. subsCapsBtn.focus();
  17757. } else if (ccBtn) {
  17758. ccBtn.focus();
  17759. }
  17760. };
  17761. return TextTrackSettings;
  17762. }(ModalDialog);
  17763. Component.registerComponent('TextTrackSettings', TextTrackSettings);
  17764. /**
  17765. * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
  17766. *
  17767. * 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}.
  17768. *
  17769. * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
  17770. * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
  17771. * @example <caption>How to disable the resize manager</caption>
  17772. * const player = videojs('#vid', {
  17773. * resizeManager: false
  17774. * });
  17775. *
  17776. * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
  17777. *
  17778. * @extends Component
  17779. */
  17780. var ResizeManager =
  17781. /*#__PURE__*/
  17782. function (_Component) {
  17783. _inheritsLoose(ResizeManager, _Component);
  17784. /**
  17785. * Create the ResizeManager.
  17786. *
  17787. * @param {Object} player
  17788. * The `Player` that this class should be attached to.
  17789. *
  17790. * @param {Object} [options]
  17791. * The key/value store of ResizeManager options.
  17792. *
  17793. * @param {Object} [options.ResizeObserver]
  17794. * A polyfill for ResizeObserver can be passed in here.
  17795. * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
  17796. */
  17797. function ResizeManager(player, options) {
  17798. var _this;
  17799. var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
  17800. if (options.ResizeObserver === null) {
  17801. RESIZE_OBSERVER_AVAILABLE = false;
  17802. } // Only create an element when ResizeObserver isn't available
  17803. var options_ = mergeOptions({
  17804. createEl: !RESIZE_OBSERVER_AVAILABLE,
  17805. reportTouchActivity: false
  17806. }, options);
  17807. _this = _Component.call(this, player, options_) || this;
  17808. _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
  17809. _this.loadListener_ = null;
  17810. _this.resizeObserver_ = null;
  17811. _this.debouncedHandler_ = debounce(function () {
  17812. _this.resizeHandler();
  17813. }, 100, false, _assertThisInitialized(_this));
  17814. if (RESIZE_OBSERVER_AVAILABLE) {
  17815. _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
  17816. _this.resizeObserver_.observe(player.el());
  17817. } else {
  17818. _this.loadListener_ = function () {
  17819. if (!_this.el_ || !_this.el_.contentWindow) {
  17820. return;
  17821. }
  17822. var debouncedHandler_ = _this.debouncedHandler_;
  17823. var unloadListener_ = _this.unloadListener_ = function () {
  17824. off(this, 'resize', debouncedHandler_);
  17825. off(this, 'unload', unloadListener_);
  17826. unloadListener_ = null;
  17827. }; // safari and edge can unload the iframe before resizemanager dispose
  17828. // we have to dispose of event handlers correctly before that happens
  17829. on(_this.el_.contentWindow, 'unload', unloadListener_);
  17830. on(_this.el_.contentWindow, 'resize', debouncedHandler_);
  17831. };
  17832. _this.one('load', _this.loadListener_);
  17833. }
  17834. return _this;
  17835. }
  17836. var _proto = ResizeManager.prototype;
  17837. _proto.createEl = function createEl() {
  17838. return _Component.prototype.createEl.call(this, 'iframe', {
  17839. className: 'vjs-resize-manager',
  17840. tabIndex: -1
  17841. }, {
  17842. 'aria-hidden': 'true'
  17843. });
  17844. }
  17845. /**
  17846. * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
  17847. *
  17848. * @fires Player#playerresize
  17849. */
  17850. ;
  17851. _proto.resizeHandler = function resizeHandler() {
  17852. /**
  17853. * Called when the player size has changed
  17854. *
  17855. * @event Player#playerresize
  17856. * @type {EventTarget~Event}
  17857. */
  17858. // make sure player is still around to trigger
  17859. // prevents this from causing an error after dispose
  17860. if (!this.player_ || !this.player_.trigger) {
  17861. return;
  17862. }
  17863. this.player_.trigger('playerresize');
  17864. };
  17865. _proto.dispose = function dispose() {
  17866. if (this.debouncedHandler_) {
  17867. this.debouncedHandler_.cancel();
  17868. }
  17869. if (this.resizeObserver_) {
  17870. if (this.player_.el()) {
  17871. this.resizeObserver_.unobserve(this.player_.el());
  17872. }
  17873. this.resizeObserver_.disconnect();
  17874. }
  17875. if (this.loadListener_) {
  17876. this.off('load', this.loadListener_);
  17877. }
  17878. if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
  17879. this.unloadListener_.call(this.el_.contentWindow);
  17880. }
  17881. this.ResizeObserver = null;
  17882. this.resizeObserver = null;
  17883. this.debouncedHandler_ = null;
  17884. this.loadListener_ = null;
  17885. _Component.prototype.dispose.call(this);
  17886. };
  17887. return ResizeManager;
  17888. }(Component);
  17889. Component.registerComponent('ResizeManager', ResizeManager);
  17890. /* track when we are at the live edge, and other helpers for live playback */
  17891. var LiveTracker =
  17892. /*#__PURE__*/
  17893. function (_Component) {
  17894. _inheritsLoose(LiveTracker, _Component);
  17895. function LiveTracker(player, options) {
  17896. var _this;
  17897. // LiveTracker does not need an element
  17898. var options_ = mergeOptions({
  17899. createEl: false
  17900. }, options);
  17901. _this = _Component.call(this, player, options_) || this;
  17902. _this.reset_();
  17903. _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden,
  17904. // also, tracking when the document is hidden can
  17905. // cause the CPU to spike and eventually crash the page on IE11.
  17906. if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
  17907. _this.on(document, 'visibilitychange', _this.handleVisibilityChange);
  17908. }
  17909. return _this;
  17910. }
  17911. var _proto = LiveTracker.prototype;
  17912. _proto.handleVisibilityChange = function handleVisibilityChange() {
  17913. if (this.player_.duration() !== Infinity) {
  17914. return;
  17915. }
  17916. if (document.hidden) {
  17917. this.stopTracking();
  17918. } else {
  17919. this.startTracking();
  17920. }
  17921. };
  17922. _proto.isBehind_ = function isBehind_() {
  17923. // don't report that we are behind until a timeupdate has been seen
  17924. if (!this.timeupdateSeen_) {
  17925. return false;
  17926. }
  17927. var liveCurrentTime = this.liveCurrentTime();
  17928. var currentTime = this.player_.currentTime();
  17929. var seekableIncrement = this.seekableIncrement_; // the live edge window is the amount of seconds away from live
  17930. // that a player can be, but still be considered live.
  17931. // we add 0.07 because the live tracking happens every 30ms
  17932. // and we want some wiggle room for short segment live playback
  17933. var liveEdgeWindow = seekableIncrement * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd
  17934. // can be Infinity, so we handle that case.
  17935. return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime;
  17936. } // all the functionality for tracking when seek end changes
  17937. // and for tracking how far past seek end we should be
  17938. ;
  17939. _proto.trackLive_ = function trackLive_() {
  17940. this.pastSeekEnd_ = this.pastSeekEnd_;
  17941. var seekable = this.player_.seekable(); // skip undefined seekable
  17942. if (!seekable || !seekable.length) {
  17943. return;
  17944. }
  17945. var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes
  17946. // once we detect that seekable has changed we check the new seek
  17947. // end against current time, with a fudge value of half a second.
  17948. if (newSeekEnd !== this.lastSeekEnd_) {
  17949. if (this.lastSeekEnd_) {
  17950. this.seekableIncrement_ = Math.abs(newSeekEnd - this.lastSeekEnd_);
  17951. }
  17952. this.pastSeekEnd_ = 0;
  17953. this.lastSeekEnd_ = newSeekEnd;
  17954. this.trigger('seekableendchange');
  17955. }
  17956. this.pastSeekEnd_ = this.pastSeekEnd() + 0.03;
  17957. if (this.isBehind_() !== this.behindLiveEdge()) {
  17958. this.behindLiveEdge_ = this.isBehind_();
  17959. this.trigger('liveedgechange');
  17960. }
  17961. }
  17962. /**
  17963. * handle a durationchange event on the player
  17964. * and start/stop tracking accordingly.
  17965. */
  17966. ;
  17967. _proto.handleDurationchange = function handleDurationchange() {
  17968. if (this.player_.duration() === Infinity) {
  17969. this.startTracking();
  17970. } else {
  17971. this.stopTracking();
  17972. }
  17973. }
  17974. /**
  17975. * start tracking live playback
  17976. */
  17977. ;
  17978. _proto.startTracking = function startTracking() {
  17979. var _this2 = this;
  17980. if (this.isTracking()) {
  17981. return;
  17982. } // If we haven't seen a timeupdate, we need to check whether playback
  17983. // began before this component started tracking. This can happen commonly
  17984. // when using autoplay.
  17985. if (!this.timeupdateSeen_) {
  17986. this.timeupdateSeen_ = this.player_.hasStarted();
  17987. }
  17988. this.trackingInterval_ = this.setInterval(this.trackLive_, 30);
  17989. this.trackLive_();
  17990. this.on(this.player_, 'play', this.trackLive_);
  17991. this.on(this.player_, 'pause', this.trackLive_); // this is to prevent showing that we are not live
  17992. // before a video starts to play
  17993. if (!this.timeupdateSeen_) {
  17994. this.one(this.player_, 'play', this.handlePlay);
  17995. this.handleTimeupdate = function () {
  17996. _this2.timeupdateSeen_ = true;
  17997. _this2.handleTimeupdate = null;
  17998. };
  17999. this.one(this.player_, 'timeupdate', this.handleTimeupdate);
  18000. }
  18001. };
  18002. _proto.handlePlay = function handlePlay() {
  18003. this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
  18004. }
  18005. /**
  18006. * Stop tracking, and set all internal variables to
  18007. * their initial value.
  18008. */
  18009. ;
  18010. _proto.reset_ = function reset_() {
  18011. this.pastSeekEnd_ = 0;
  18012. this.lastSeekEnd_ = null;
  18013. this.behindLiveEdge_ = null;
  18014. this.timeupdateSeen_ = false;
  18015. this.clearInterval(this.trackingInterval_);
  18016. this.trackingInterval_ = null;
  18017. this.seekableIncrement_ = 12;
  18018. this.off(this.player_, 'play', this.trackLive_);
  18019. this.off(this.player_, 'pause', this.trackLive_);
  18020. this.off(this.player_, 'play', this.handlePlay);
  18021. this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
  18022. if (this.handleTimeupdate) {
  18023. this.off(this.player_, 'timeupdate', this.handleTimeupdate);
  18024. this.handleTimeupdate = null;
  18025. }
  18026. }
  18027. /**
  18028. * stop tracking live playback
  18029. */
  18030. ;
  18031. _proto.stopTracking = function stopTracking() {
  18032. if (!this.isTracking()) {
  18033. return;
  18034. }
  18035. this.reset_();
  18036. }
  18037. /**
  18038. * A helper to get the player seekable end
  18039. * so that we don't have to null check everywhere
  18040. */
  18041. ;
  18042. _proto.seekableEnd = function seekableEnd() {
  18043. var seekable = this.player_.seekable();
  18044. var seekableEnds = [];
  18045. var i = seekable ? seekable.length : 0;
  18046. while (i--) {
  18047. seekableEnds.push(seekable.end(i));
  18048. } // grab the furthest seekable end after sorting, or if there are none
  18049. // default to Infinity
  18050. return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
  18051. }
  18052. /**
  18053. * A helper to get the player seekable start
  18054. * so that we don't have to null check everywhere
  18055. */
  18056. ;
  18057. _proto.seekableStart = function seekableStart() {
  18058. var seekable = this.player_.seekable();
  18059. var seekableStarts = [];
  18060. var i = seekable ? seekable.length : 0;
  18061. while (i--) {
  18062. seekableStarts.push(seekable.start(i));
  18063. } // grab the first seekable start after sorting, or if there are none
  18064. // default to 0
  18065. return seekableStarts.length ? seekableStarts.sort()[0] : 0;
  18066. }
  18067. /**
  18068. * Get the live time window
  18069. */
  18070. ;
  18071. _proto.liveWindow = function liveWindow() {
  18072. var liveCurrentTime = this.liveCurrentTime();
  18073. if (liveCurrentTime === Infinity) {
  18074. return Infinity;
  18075. }
  18076. return liveCurrentTime - this.seekableStart();
  18077. }
  18078. /**
  18079. * Determines if the player is live, only checks if this component
  18080. * is tracking live playback or not
  18081. */
  18082. ;
  18083. _proto.isLive = function isLive() {
  18084. return this.isTracking();
  18085. }
  18086. /**
  18087. * Determines if currentTime is at the live edge and won't fall behind
  18088. * on each seekableendchange
  18089. */
  18090. ;
  18091. _proto.atLiveEdge = function atLiveEdge() {
  18092. return !this.behindLiveEdge();
  18093. }
  18094. /**
  18095. * get what we expect the live current time to be
  18096. */
  18097. ;
  18098. _proto.liveCurrentTime = function liveCurrentTime() {
  18099. return this.pastSeekEnd() + this.seekableEnd();
  18100. }
  18101. /**
  18102. * Returns how far past seek end we expect current time to be
  18103. */
  18104. ;
  18105. _proto.pastSeekEnd = function pastSeekEnd() {
  18106. return this.pastSeekEnd_;
  18107. }
  18108. /**
  18109. * If we are currently behind the live edge, aka currentTime will be
  18110. * behind on a seekableendchange
  18111. */
  18112. ;
  18113. _proto.behindLiveEdge = function behindLiveEdge() {
  18114. return this.behindLiveEdge_;
  18115. };
  18116. _proto.isTracking = function isTracking() {
  18117. return typeof this.trackingInterval_ === 'number';
  18118. }
  18119. /**
  18120. * Seek to the live edge if we are behind the live edge
  18121. */
  18122. ;
  18123. _proto.seekToLiveEdge = function seekToLiveEdge() {
  18124. if (this.atLiveEdge()) {
  18125. return;
  18126. }
  18127. this.player_.currentTime(this.liveCurrentTime());
  18128. if (this.player_.paused()) {
  18129. this.player_.play();
  18130. }
  18131. };
  18132. _proto.dispose = function dispose() {
  18133. this.stopTracking();
  18134. _Component.prototype.dispose.call(this);
  18135. };
  18136. return LiveTracker;
  18137. }(Component);
  18138. Component.registerComponent('LiveTracker', LiveTracker);
  18139. /**
  18140. * This function is used to fire a sourceset when there is something
  18141. * similar to `mediaEl.load()` being called. It will try to find the source via
  18142. * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
  18143. * with the source that was found or empty string if we cannot know. If it cannot
  18144. * find a source then `sourceset` will not be fired.
  18145. *
  18146. * @param {Html5} tech
  18147. * The tech object that sourceset was setup on
  18148. *
  18149. * @return {boolean}
  18150. * returns false if the sourceset was not fired and true otherwise.
  18151. */
  18152. var sourcesetLoad = function sourcesetLoad(tech) {
  18153. var el = tech.el(); // if `el.src` is set, that source will be loaded.
  18154. if (el.hasAttribute('src')) {
  18155. tech.triggerSourceset(el.src);
  18156. return true;
  18157. }
  18158. /**
  18159. * Since there isn't a src property on the media element, source elements will be used for
  18160. * implementing the source selection algorithm. This happens asynchronously and
  18161. * for most cases were there is more than one source we cannot tell what source will
  18162. * be loaded, without re-implementing the source selection algorithm. At this time we are not
  18163. * going to do that. There are three special cases that we do handle here though:
  18164. *
  18165. * 1. If there are no sources, do not fire `sourceset`.
  18166. * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
  18167. * 3. If there is more than one `<source>` but all of them have the same `src` url.
  18168. * That will be our src.
  18169. */
  18170. var sources = tech.$$('source');
  18171. var srcUrls = [];
  18172. var src = ''; // if there are no sources, do not fire sourceset
  18173. if (!sources.length) {
  18174. return false;
  18175. } // only count valid/non-duplicate source elements
  18176. for (var i = 0; i < sources.length; i++) {
  18177. var url = sources[i].src;
  18178. if (url && srcUrls.indexOf(url) === -1) {
  18179. srcUrls.push(url);
  18180. }
  18181. } // there were no valid sources
  18182. if (!srcUrls.length) {
  18183. return false;
  18184. } // there is only one valid source element url
  18185. // use that
  18186. if (srcUrls.length === 1) {
  18187. src = srcUrls[0];
  18188. }
  18189. tech.triggerSourceset(src);
  18190. return true;
  18191. };
  18192. /**
  18193. * our implementation of an `innerHTML` descriptor for browsers
  18194. * that do not have one.
  18195. */
  18196. var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  18197. get: function get() {
  18198. return this.cloneNode(true).innerHTML;
  18199. },
  18200. set: function set(v) {
  18201. // make a dummy node to use innerHTML on
  18202. var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
  18203. dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
  18204. var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
  18205. // to the document fragment
  18206. while (dummy.childNodes.length) {
  18207. docFrag.appendChild(dummy.childNodes[0]);
  18208. } // remove content
  18209. this.innerText = ''; // now we add all of that html in one by appending the
  18210. // document fragment. This is how innerHTML does it.
  18211. window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
  18212. return this.innerHTML;
  18213. }
  18214. });
  18215. /**
  18216. * Get a property descriptor given a list of priorities and the
  18217. * property to get.
  18218. */
  18219. var getDescriptor = function getDescriptor(priority, prop) {
  18220. var descriptor = {};
  18221. for (var i = 0; i < priority.length; i++) {
  18222. descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
  18223. if (descriptor && descriptor.set && descriptor.get) {
  18224. break;
  18225. }
  18226. }
  18227. descriptor.enumerable = true;
  18228. descriptor.configurable = true;
  18229. return descriptor;
  18230. };
  18231. var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
  18232. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
  18233. };
  18234. /**
  18235. * Patches browser internal functions so that we can tell synchronously
  18236. * if a `<source>` was appended to the media element. For some reason this
  18237. * causes a `sourceset` if the the media element is ready and has no source.
  18238. * This happens when:
  18239. * - The page has just loaded and the media element does not have a source.
  18240. * - The media element was emptied of all sources, then `load()` was called.
  18241. *
  18242. * It does this by patching the following functions/properties when they are supported:
  18243. *
  18244. * - `append()` - can be used to add a `<source>` element to the media element
  18245. * - `appendChild()` - can be used to add a `<source>` element to the media element
  18246. * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
  18247. * - `innerHTML` - can be used to add a `<source>` element to the media element
  18248. *
  18249. * @param {Html5} tech
  18250. * The tech object that sourceset is being setup on.
  18251. */
  18252. var firstSourceWatch = function firstSourceWatch(tech) {
  18253. var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
  18254. if (el.resetSourceWatch_) {
  18255. return;
  18256. }
  18257. var old = {};
  18258. var innerDescriptor = getInnerHTMLDescriptor(tech);
  18259. var appendWrapper = function appendWrapper(appendFn) {
  18260. return function () {
  18261. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  18262. args[_key] = arguments[_key];
  18263. }
  18264. var retval = appendFn.apply(el, args);
  18265. sourcesetLoad(tech);
  18266. return retval;
  18267. };
  18268. };
  18269. ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
  18270. if (!el[k]) {
  18271. return;
  18272. } // store the old function
  18273. old[k] = el[k]; // call the old function with a sourceset if a source
  18274. // was loaded
  18275. el[k] = appendWrapper(old[k]);
  18276. });
  18277. Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
  18278. set: appendWrapper(innerDescriptor.set)
  18279. }));
  18280. el.resetSourceWatch_ = function () {
  18281. el.resetSourceWatch_ = null;
  18282. Object.keys(old).forEach(function (k) {
  18283. el[k] = old[k];
  18284. });
  18285. Object.defineProperty(el, 'innerHTML', innerDescriptor);
  18286. }; // on the first sourceset, we need to revert our changes
  18287. tech.one('sourceset', el.resetSourceWatch_);
  18288. };
  18289. /**
  18290. * our implementation of a `src` descriptor for browsers
  18291. * that do not have one.
  18292. */
  18293. var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  18294. get: function get() {
  18295. if (this.hasAttribute('src')) {
  18296. return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
  18297. }
  18298. return '';
  18299. },
  18300. set: function set(v) {
  18301. window$1.Element.prototype.setAttribute.call(this, 'src', v);
  18302. return v;
  18303. }
  18304. });
  18305. var getSrcDescriptor = function getSrcDescriptor(tech) {
  18306. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
  18307. };
  18308. /**
  18309. * setup `sourceset` handling on the `Html5` tech. This function
  18310. * patches the following element properties/functions:
  18311. *
  18312. * - `src` - to determine when `src` is set
  18313. * - `setAttribute()` - to determine when `src` is set
  18314. * - `load()` - this re-triggers the source selection algorithm, and can
  18315. * cause a sourceset.
  18316. *
  18317. * If there is no source when we are adding `sourceset` support or during a `load()`
  18318. * we also patch the functions listed in `firstSourceWatch`.
  18319. *
  18320. * @param {Html5} tech
  18321. * The tech to patch
  18322. */
  18323. var setupSourceset = function setupSourceset(tech) {
  18324. if (!tech.featuresSourceset) {
  18325. return;
  18326. }
  18327. var el = tech.el(); // make sure sourceset isn't setup twice.
  18328. if (el.resetSourceset_) {
  18329. return;
  18330. }
  18331. var srcDescriptor = getSrcDescriptor(tech);
  18332. var oldSetAttribute = el.setAttribute;
  18333. var oldLoad = el.load;
  18334. Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
  18335. set: function set(v) {
  18336. var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
  18337. tech.triggerSourceset(el.src);
  18338. return retval;
  18339. }
  18340. }));
  18341. el.setAttribute = function (n, v) {
  18342. var retval = oldSetAttribute.call(el, n, v);
  18343. if (/src/i.test(n)) {
  18344. tech.triggerSourceset(el.src);
  18345. }
  18346. return retval;
  18347. };
  18348. el.load = function () {
  18349. var retval = oldLoad.call(el); // if load was called, but there was no source to fire
  18350. // sourceset on. We have to watch for a source append
  18351. // as that can trigger a `sourceset` when the media element
  18352. // has no source
  18353. if (!sourcesetLoad(tech)) {
  18354. tech.triggerSourceset('');
  18355. firstSourceWatch(tech);
  18356. }
  18357. return retval;
  18358. };
  18359. if (el.currentSrc) {
  18360. tech.triggerSourceset(el.currentSrc);
  18361. } else if (!sourcesetLoad(tech)) {
  18362. firstSourceWatch(tech);
  18363. }
  18364. el.resetSourceset_ = function () {
  18365. el.resetSourceset_ = null;
  18366. el.load = oldLoad;
  18367. el.setAttribute = oldSetAttribute;
  18368. Object.defineProperty(el, 'src', srcDescriptor);
  18369. if (el.resetSourceWatch_) {
  18370. el.resetSourceWatch_();
  18371. }
  18372. };
  18373. };
  18374. function _templateObject$1() {
  18375. 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."]);
  18376. _templateObject$1 = function _templateObject() {
  18377. return data;
  18378. };
  18379. return data;
  18380. }
  18381. /**
  18382. * HTML5 Media Controller - Wrapper for HTML5 Media API
  18383. *
  18384. * @mixes Tech~SourceHandlerAdditions
  18385. * @extends Tech
  18386. */
  18387. var Html5 =
  18388. /*#__PURE__*/
  18389. function (_Tech) {
  18390. _inheritsLoose(Html5, _Tech);
  18391. /**
  18392. * Create an instance of this Tech.
  18393. *
  18394. * @param {Object} [options]
  18395. * The key/value store of player options.
  18396. *
  18397. * @param {Component~ReadyCallback} ready
  18398. * Callback function to call when the `HTML5` Tech is ready.
  18399. */
  18400. function Html5(options, ready) {
  18401. var _this;
  18402. _this = _Tech.call(this, options, ready) || this;
  18403. var source = options.source;
  18404. var crossoriginTracks = false; // Set the source if one is provided
  18405. // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
  18406. // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
  18407. // anyway so the error gets fired.
  18408. if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
  18409. _this.setSource(source);
  18410. } else {
  18411. _this.handleLateInit_(_this.el_);
  18412. } // setup sourceset after late sourceset/init
  18413. if (options.enableSourceset) {
  18414. _this.setupSourcesetHandling_();
  18415. }
  18416. if (_this.el_.hasChildNodes()) {
  18417. var nodes = _this.el_.childNodes;
  18418. var nodesLength = nodes.length;
  18419. var removeNodes = [];
  18420. while (nodesLength--) {
  18421. var node = nodes[nodesLength];
  18422. var nodeName = node.nodeName.toLowerCase();
  18423. if (nodeName === 'track') {
  18424. if (!_this.featuresNativeTextTracks) {
  18425. // Empty video tag tracks so the built-in player doesn't use them also.
  18426. // This may not be fast enough to stop HTML5 browsers from reading the tags
  18427. // so we'll need to turn off any default tracks if we're manually doing
  18428. // captions and subtitles. videoElement.textTracks
  18429. removeNodes.push(node);
  18430. } else {
  18431. // store HTMLTrackElement and TextTrack to remote list
  18432. _this.remoteTextTrackEls().addTrackElement_(node);
  18433. _this.remoteTextTracks().addTrack(node.track);
  18434. _this.textTracks().addTrack(node.track);
  18435. if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
  18436. crossoriginTracks = true;
  18437. }
  18438. }
  18439. }
  18440. }
  18441. for (var i = 0; i < removeNodes.length; i++) {
  18442. _this.el_.removeChild(removeNodes[i]);
  18443. }
  18444. }
  18445. _this.proxyNativeTracks_();
  18446. if (_this.featuresNativeTextTracks && crossoriginTracks) {
  18447. log.warn(tsml(_templateObject$1()));
  18448. } // prevent iOS Safari from disabling metadata text tracks during native playback
  18449. _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
  18450. // Our goal should be to get the custom controls on mobile solid everywhere
  18451. // so we can remove this all together. Right now this will block custom
  18452. // controls on touch enabled laptops like the Chrome Pixel
  18453. if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
  18454. _this.setControls(true);
  18455. } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
  18456. // into a `fullscreenchange` event
  18457. _this.proxyWebkitFullscreen_();
  18458. _this.triggerReady();
  18459. return _this;
  18460. }
  18461. /**
  18462. * Dispose of `HTML5` media element and remove all tracks.
  18463. */
  18464. var _proto = Html5.prototype;
  18465. _proto.dispose = function dispose() {
  18466. if (this.el_ && this.el_.resetSourceset_) {
  18467. this.el_.resetSourceset_();
  18468. }
  18469. Html5.disposeMediaElement(this.el_);
  18470. this.options_ = null; // tech will handle clearing of the emulated track list
  18471. _Tech.prototype.dispose.call(this);
  18472. }
  18473. /**
  18474. * Modify the media element so that we can detect when
  18475. * the source is changed. Fires `sourceset` just after the source has changed
  18476. */
  18477. ;
  18478. _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
  18479. setupSourceset(this);
  18480. }
  18481. /**
  18482. * When a captions track is enabled in the iOS Safari native player, all other
  18483. * tracks are disabled (including metadata tracks), which nulls all of their
  18484. * associated cue points. This will restore metadata tracks to their pre-fullscreen
  18485. * state in those cases so that cue points are not needlessly lost.
  18486. *
  18487. * @private
  18488. */
  18489. ;
  18490. _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
  18491. var textTracks = this.textTracks();
  18492. var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
  18493. var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
  18494. metadataTracksPreFullscreenState = [];
  18495. for (var i = 0; i < textTracks.length; i++) {
  18496. var track = textTracks[i];
  18497. if (track.kind === 'metadata') {
  18498. metadataTracksPreFullscreenState.push({
  18499. track: track,
  18500. storedMode: track.mode
  18501. });
  18502. }
  18503. }
  18504. }; // snapshot each metadata track's initial state, and update the snapshot
  18505. // each time there is a track 'change' event
  18506. takeMetadataTrackSnapshot();
  18507. textTracks.addEventListener('change', takeMetadataTrackSnapshot);
  18508. this.on('dispose', function () {
  18509. return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  18510. });
  18511. var restoreTrackMode = function restoreTrackMode() {
  18512. for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
  18513. var storedTrack = metadataTracksPreFullscreenState[i];
  18514. if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
  18515. storedTrack.track.mode = storedTrack.storedMode;
  18516. }
  18517. } // we only want this handler to be executed on the first 'change' event
  18518. textTracks.removeEventListener('change', restoreTrackMode);
  18519. }; // when we enter fullscreen playback, stop updating the snapshot and
  18520. // restore all track modes to their pre-fullscreen state
  18521. this.on('webkitbeginfullscreen', function () {
  18522. textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
  18523. textTracks.removeEventListener('change', restoreTrackMode);
  18524. textTracks.addEventListener('change', restoreTrackMode);
  18525. }); // start updating the snapshot again after leaving fullscreen
  18526. this.on('webkitendfullscreen', function () {
  18527. // remove the listener before adding it just in case it wasn't previously removed
  18528. textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  18529. textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
  18530. textTracks.removeEventListener('change', restoreTrackMode);
  18531. });
  18532. }
  18533. /**
  18534. * Attempt to force override of tracks for the given type
  18535. *
  18536. * @param {string} type - Track type to override, possible values include 'Audio',
  18537. * 'Video', and 'Text'.
  18538. * @param {boolean} override - If set to true native audio/video will be overridden,
  18539. * otherwise native audio/video will potentially be used.
  18540. * @private
  18541. */
  18542. ;
  18543. _proto.overrideNative_ = function overrideNative_(type, override) {
  18544. var _this2 = this;
  18545. // If there is no behavioral change don't add/remove listeners
  18546. if (override !== this["featuresNative" + type + "Tracks"]) {
  18547. return;
  18548. }
  18549. var lowerCaseType = type.toLowerCase();
  18550. if (this[lowerCaseType + "TracksListeners_"]) {
  18551. Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) {
  18552. var elTracks = _this2.el()[lowerCaseType + "Tracks"];
  18553. elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
  18554. });
  18555. }
  18556. this["featuresNative" + type + "Tracks"] = !override;
  18557. this[lowerCaseType + "TracksListeners_"] = null;
  18558. this.proxyNativeTracksForType_(lowerCaseType);
  18559. }
  18560. /**
  18561. * Attempt to force override of native audio tracks.
  18562. *
  18563. * @param {boolean} override - If set to true native audio will be overridden,
  18564. * otherwise native audio will potentially be used.
  18565. */
  18566. ;
  18567. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
  18568. this.overrideNative_('Audio', override);
  18569. }
  18570. /**
  18571. * Attempt to force override of native video tracks.
  18572. *
  18573. * @param {boolean} override - If set to true native video will be overridden,
  18574. * otherwise native video will potentially be used.
  18575. */
  18576. ;
  18577. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
  18578. this.overrideNative_('Video', override);
  18579. }
  18580. /**
  18581. * Proxy native track list events for the given type to our track
  18582. * lists if the browser we are playing in supports that type of track list.
  18583. *
  18584. * @param {string} name - Track type; values include 'audio', 'video', and 'text'
  18585. * @private
  18586. */
  18587. ;
  18588. _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
  18589. var _this3 = this;
  18590. var props = NORMAL[name];
  18591. var elTracks = this.el()[props.getterName];
  18592. var techTracks = this[props.getterName]();
  18593. if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
  18594. return;
  18595. }
  18596. var listeners = {
  18597. change: function change(e) {
  18598. techTracks.trigger({
  18599. type: 'change',
  18600. target: techTracks,
  18601. currentTarget: techTracks,
  18602. srcElement: techTracks
  18603. });
  18604. },
  18605. addtrack: function addtrack(e) {
  18606. techTracks.addTrack(e.track);
  18607. },
  18608. removetrack: function removetrack(e) {
  18609. techTracks.removeTrack(e.track);
  18610. }
  18611. };
  18612. var removeOldTracks = function removeOldTracks() {
  18613. var removeTracks = [];
  18614. for (var i = 0; i < techTracks.length; i++) {
  18615. var found = false;
  18616. for (var j = 0; j < elTracks.length; j++) {
  18617. if (elTracks[j] === techTracks[i]) {
  18618. found = true;
  18619. break;
  18620. }
  18621. }
  18622. if (!found) {
  18623. removeTracks.push(techTracks[i]);
  18624. }
  18625. }
  18626. while (removeTracks.length) {
  18627. techTracks.removeTrack(removeTracks.shift());
  18628. }
  18629. };
  18630. this[props.getterName + 'Listeners_'] = listeners;
  18631. Object.keys(listeners).forEach(function (eventName) {
  18632. var listener = listeners[eventName];
  18633. elTracks.addEventListener(eventName, listener);
  18634. _this3.on('dispose', function (e) {
  18635. return elTracks.removeEventListener(eventName, listener);
  18636. });
  18637. }); // Remove (native) tracks that are not used anymore
  18638. this.on('loadstart', removeOldTracks);
  18639. this.on('dispose', function (e) {
  18640. return _this3.off('loadstart', removeOldTracks);
  18641. });
  18642. }
  18643. /**
  18644. * Proxy all native track list events to our track lists if the browser we are playing
  18645. * in supports that type of track list.
  18646. *
  18647. * @private
  18648. */
  18649. ;
  18650. _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
  18651. var _this4 = this;
  18652. NORMAL.names.forEach(function (name) {
  18653. _this4.proxyNativeTracksForType_(name);
  18654. });
  18655. }
  18656. /**
  18657. * Create the `Html5` Tech's DOM element.
  18658. *
  18659. * @return {Element}
  18660. * The element that gets created.
  18661. */
  18662. ;
  18663. _proto.createEl = function createEl() {
  18664. var el = this.options_.tag; // Check if this browser supports moving the element into the box.
  18665. // On the iPhone video will break if you move the element,
  18666. // So we have to create a brand new element.
  18667. // If we ingested the player div, we do not need to move the media element.
  18668. if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
  18669. // If the original tag is still there, clone and remove it.
  18670. if (el) {
  18671. var clone = el.cloneNode(true);
  18672. if (el.parentNode) {
  18673. el.parentNode.insertBefore(clone, el);
  18674. }
  18675. Html5.disposeMediaElement(el);
  18676. el = clone;
  18677. } else {
  18678. el = document.createElement('video'); // determine if native controls should be used
  18679. var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
  18680. var attributes = mergeOptions({}, tagAttributes);
  18681. if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
  18682. delete attributes.controls;
  18683. }
  18684. setAttributes(el, assign(attributes, {
  18685. id: this.options_.techId,
  18686. "class": 'vjs-tech'
  18687. }));
  18688. }
  18689. el.playerId = this.options_.playerId;
  18690. }
  18691. if (typeof this.options_.preload !== 'undefined') {
  18692. setAttribute(el, 'preload', this.options_.preload);
  18693. } // Update specific tag settings, in case they were overridden
  18694. // `autoplay` has to be *last* so that `muted` and `playsinline` are present
  18695. // when iOS/Safari or other browsers attempt to autoplay.
  18696. var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
  18697. for (var i = 0; i < settingsAttrs.length; i++) {
  18698. var attr = settingsAttrs[i];
  18699. var value = this.options_[attr];
  18700. if (typeof value !== 'undefined') {
  18701. if (value) {
  18702. setAttribute(el, attr, attr);
  18703. } else {
  18704. removeAttribute(el, attr);
  18705. }
  18706. el[attr] = value;
  18707. }
  18708. }
  18709. return el;
  18710. }
  18711. /**
  18712. * This will be triggered if the loadstart event has already fired, before videojs was
  18713. * ready. Two known examples of when this can happen are:
  18714. * 1. If we're loading the playback object after it has started loading
  18715. * 2. The media is already playing the (often with autoplay on) then
  18716. *
  18717. * This function will fire another loadstart so that videojs can catchup.
  18718. *
  18719. * @fires Tech#loadstart
  18720. *
  18721. * @return {undefined}
  18722. * returns nothing.
  18723. */
  18724. ;
  18725. _proto.handleLateInit_ = function handleLateInit_(el) {
  18726. if (el.networkState === 0 || el.networkState === 3) {
  18727. // The video element hasn't started loading the source yet
  18728. // or didn't find a source
  18729. return;
  18730. }
  18731. if (el.readyState === 0) {
  18732. // NetworkState is set synchronously BUT loadstart is fired at the
  18733. // end of the current stack, usually before setInterval(fn, 0).
  18734. // So at this point we know loadstart may have already fired or is
  18735. // about to fire, and either way the player hasn't seen it yet.
  18736. // We don't want to fire loadstart prematurely here and cause a
  18737. // double loadstart so we'll wait and see if it happens between now
  18738. // and the next loop, and fire it if not.
  18739. // HOWEVER, we also want to make sure it fires before loadedmetadata
  18740. // which could also happen between now and the next loop, so we'll
  18741. // watch for that also.
  18742. var loadstartFired = false;
  18743. var setLoadstartFired = function setLoadstartFired() {
  18744. loadstartFired = true;
  18745. };
  18746. this.on('loadstart', setLoadstartFired);
  18747. var triggerLoadstart = function triggerLoadstart() {
  18748. // We did miss the original loadstart. Make sure the player
  18749. // sees loadstart before loadedmetadata
  18750. if (!loadstartFired) {
  18751. this.trigger('loadstart');
  18752. }
  18753. };
  18754. this.on('loadedmetadata', triggerLoadstart);
  18755. this.ready(function () {
  18756. this.off('loadstart', setLoadstartFired);
  18757. this.off('loadedmetadata', triggerLoadstart);
  18758. if (!loadstartFired) {
  18759. // We did miss the original native loadstart. Fire it now.
  18760. this.trigger('loadstart');
  18761. }
  18762. });
  18763. return;
  18764. } // From here on we know that loadstart already fired and we missed it.
  18765. // The other readyState events aren't as much of a problem if we double
  18766. // them, so not going to go to as much trouble as loadstart to prevent
  18767. // that unless we find reason to.
  18768. var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
  18769. eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
  18770. if (el.readyState >= 2) {
  18771. eventsToTrigger.push('loadeddata');
  18772. } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
  18773. if (el.readyState >= 3) {
  18774. eventsToTrigger.push('canplay');
  18775. } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
  18776. if (el.readyState >= 4) {
  18777. eventsToTrigger.push('canplaythrough');
  18778. } // We still need to give the player time to add event listeners
  18779. this.ready(function () {
  18780. eventsToTrigger.forEach(function (type) {
  18781. this.trigger(type);
  18782. }, this);
  18783. });
  18784. }
  18785. /**
  18786. * Set current time for the `HTML5` tech.
  18787. *
  18788. * @param {number} seconds
  18789. * Set the current time of the media to this.
  18790. */
  18791. ;
  18792. _proto.setCurrentTime = function setCurrentTime(seconds) {
  18793. try {
  18794. this.el_.currentTime = seconds;
  18795. } catch (e) {
  18796. log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
  18797. }
  18798. }
  18799. /**
  18800. * Get the current duration of the HTML5 media element.
  18801. *
  18802. * @return {number}
  18803. * The duration of the media or 0 if there is no duration.
  18804. */
  18805. ;
  18806. _proto.duration = function duration() {
  18807. var _this5 = this;
  18808. // Android Chrome will report duration as Infinity for VOD HLS until after
  18809. // playback has started, which triggers the live display erroneously.
  18810. // Return NaN if playback has not started and trigger a durationupdate once
  18811. // the duration can be reliably known.
  18812. if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
  18813. // Wait for the first `timeupdate` with currentTime > 0 - there may be
  18814. // several with 0
  18815. var checkProgress = function checkProgress() {
  18816. if (_this5.el_.currentTime > 0) {
  18817. // Trigger durationchange for genuinely live video
  18818. if (_this5.el_.duration === Infinity) {
  18819. _this5.trigger('durationchange');
  18820. }
  18821. _this5.off('timeupdate', checkProgress);
  18822. }
  18823. };
  18824. this.on('timeupdate', checkProgress);
  18825. return NaN;
  18826. }
  18827. return this.el_.duration || NaN;
  18828. }
  18829. /**
  18830. * Get the current width of the HTML5 media element.
  18831. *
  18832. * @return {number}
  18833. * The width of the HTML5 media element.
  18834. */
  18835. ;
  18836. _proto.width = function width() {
  18837. return this.el_.offsetWidth;
  18838. }
  18839. /**
  18840. * Get the current height of the HTML5 media element.
  18841. *
  18842. * @return {number}
  18843. * The height of the HTML5 media element.
  18844. */
  18845. ;
  18846. _proto.height = function height() {
  18847. return this.el_.offsetHeight;
  18848. }
  18849. /**
  18850. * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
  18851. * `fullscreenchange` event.
  18852. *
  18853. * @private
  18854. * @fires fullscreenchange
  18855. * @listens webkitendfullscreen
  18856. * @listens webkitbeginfullscreen
  18857. * @listens webkitbeginfullscreen
  18858. */
  18859. ;
  18860. _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
  18861. var _this6 = this;
  18862. if (!('webkitDisplayingFullscreen' in this.el_)) {
  18863. return;
  18864. }
  18865. var endFn = function endFn() {
  18866. this.trigger('fullscreenchange', {
  18867. isFullscreen: false
  18868. });
  18869. };
  18870. var beginFn = function beginFn() {
  18871. if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
  18872. this.one('webkitendfullscreen', endFn);
  18873. this.trigger('fullscreenchange', {
  18874. isFullscreen: true
  18875. });
  18876. }
  18877. };
  18878. this.on('webkitbeginfullscreen', beginFn);
  18879. this.on('dispose', function () {
  18880. _this6.off('webkitbeginfullscreen', beginFn);
  18881. _this6.off('webkitendfullscreen', endFn);
  18882. });
  18883. }
  18884. /**
  18885. * Check if fullscreen is supported on the current playback device.
  18886. *
  18887. * @return {boolean}
  18888. * - True if fullscreen is supported.
  18889. * - False if fullscreen is not supported.
  18890. */
  18891. ;
  18892. _proto.supportsFullScreen = function supportsFullScreen() {
  18893. if (typeof this.el_.webkitEnterFullScreen === 'function') {
  18894. var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
  18895. if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
  18896. return true;
  18897. }
  18898. }
  18899. return false;
  18900. }
  18901. /**
  18902. * Request that the `HTML5` Tech enter fullscreen.
  18903. */
  18904. ;
  18905. _proto.enterFullScreen = function enterFullScreen() {
  18906. var video = this.el_;
  18907. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  18908. // attempt to prime the video element for programmatic access
  18909. // this isn't necessary on the desktop but shouldn't hurt
  18910. this.el_.play(); // playing and pausing synchronously during the transition to fullscreen
  18911. // can get iOS ~6.1 devices into a play/pause loop
  18912. this.setTimeout(function () {
  18913. video.pause();
  18914. video.webkitEnterFullScreen();
  18915. }, 0);
  18916. } else {
  18917. video.webkitEnterFullScreen();
  18918. }
  18919. }
  18920. /**
  18921. * Request that the `HTML5` Tech exit fullscreen.
  18922. */
  18923. ;
  18924. _proto.exitFullScreen = function exitFullScreen() {
  18925. this.el_.webkitExitFullScreen();
  18926. }
  18927. /**
  18928. * Create a floating video window always on top of other windows so that users may
  18929. * continue consuming media while they interact with other content sites, or
  18930. * applications on their device.
  18931. *
  18932. * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
  18933. *
  18934. * @return {Promise}
  18935. * A promise with a Picture-in-Picture window.
  18936. */
  18937. ;
  18938. _proto.requestPictureInPicture = function requestPictureInPicture() {
  18939. return this.el_.requestPictureInPicture();
  18940. }
  18941. /**
  18942. * A getter/setter for the `Html5` Tech's source object.
  18943. * > Note: Please use {@link Html5#setSource}
  18944. *
  18945. * @param {Tech~SourceObject} [src]
  18946. * The source object you want to set on the `HTML5` techs element.
  18947. *
  18948. * @return {Tech~SourceObject|undefined}
  18949. * - The current source object when a source is not passed in.
  18950. * - undefined when setting
  18951. *
  18952. * @deprecated Since version 5.
  18953. */
  18954. ;
  18955. _proto.src = function src(_src) {
  18956. if (_src === undefined) {
  18957. return this.el_.src;
  18958. } // Setting src through `src` instead of `setSrc` will be deprecated
  18959. this.setSrc(_src);
  18960. }
  18961. /**
  18962. * Reset the tech by removing all sources and then calling
  18963. * {@link Html5.resetMediaElement}.
  18964. */
  18965. ;
  18966. _proto.reset = function reset() {
  18967. Html5.resetMediaElement(this.el_);
  18968. }
  18969. /**
  18970. * Get the current source on the `HTML5` Tech. Falls back to returning the source from
  18971. * the HTML5 media element.
  18972. *
  18973. * @return {Tech~SourceObject}
  18974. * The current source object from the HTML5 tech. With a fallback to the
  18975. * elements source.
  18976. */
  18977. ;
  18978. _proto.currentSrc = function currentSrc() {
  18979. if (this.currentSource_) {
  18980. return this.currentSource_.src;
  18981. }
  18982. return this.el_.currentSrc;
  18983. }
  18984. /**
  18985. * Set controls attribute for the HTML5 media Element.
  18986. *
  18987. * @param {string} val
  18988. * Value to set the controls attribute to
  18989. */
  18990. ;
  18991. _proto.setControls = function setControls(val) {
  18992. this.el_.controls = !!val;
  18993. }
  18994. /**
  18995. * Create and returns a remote {@link TextTrack} object.
  18996. *
  18997. * @param {string} kind
  18998. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  18999. *
  19000. * @param {string} [label]
  19001. * Label to identify the text track
  19002. *
  19003. * @param {string} [language]
  19004. * Two letter language abbreviation
  19005. *
  19006. * @return {TextTrack}
  19007. * The TextTrack that gets created.
  19008. */
  19009. ;
  19010. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  19011. if (!this.featuresNativeTextTracks) {
  19012. return _Tech.prototype.addTextTrack.call(this, kind, label, language);
  19013. }
  19014. return this.el_.addTextTrack(kind, label, language);
  19015. }
  19016. /**
  19017. * Creates either native TextTrack or an emulated TextTrack depending
  19018. * on the value of `featuresNativeTextTracks`
  19019. *
  19020. * @param {Object} options
  19021. * The object should contain the options to initialize the TextTrack with.
  19022. *
  19023. * @param {string} [options.kind]
  19024. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  19025. *
  19026. * @param {string} [options.label]
  19027. * Label to identify the text track
  19028. *
  19029. * @param {string} [options.language]
  19030. * Two letter language abbreviation.
  19031. *
  19032. * @param {boolean} [options.default]
  19033. * Default this track to on.
  19034. *
  19035. * @param {string} [options.id]
  19036. * The internal id to assign this track.
  19037. *
  19038. * @param {string} [options.src]
  19039. * A source url for the track.
  19040. *
  19041. * @return {HTMLTrackElement}
  19042. * The track element that gets created.
  19043. */
  19044. ;
  19045. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  19046. if (!this.featuresNativeTextTracks) {
  19047. return _Tech.prototype.createRemoteTextTrack.call(this, options);
  19048. }
  19049. var htmlTrackElement = document.createElement('track');
  19050. if (options.kind) {
  19051. htmlTrackElement.kind = options.kind;
  19052. }
  19053. if (options.label) {
  19054. htmlTrackElement.label = options.label;
  19055. }
  19056. if (options.language || options.srclang) {
  19057. htmlTrackElement.srclang = options.language || options.srclang;
  19058. }
  19059. if (options["default"]) {
  19060. htmlTrackElement["default"] = options["default"];
  19061. }
  19062. if (options.id) {
  19063. htmlTrackElement.id = options.id;
  19064. }
  19065. if (options.src) {
  19066. htmlTrackElement.src = options.src;
  19067. }
  19068. return htmlTrackElement;
  19069. }
  19070. /**
  19071. * Creates a remote text track object and returns an html track element.
  19072. *
  19073. * @param {Object} options The object should contain values for
  19074. * kind, language, label, and src (location of the WebVTT file)
  19075. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  19076. * automatically removed from the video element whenever the source changes
  19077. * @return {HTMLTrackElement} An Html Track Element.
  19078. * This can be an emulated {@link HTMLTrackElement} or a native one.
  19079. * @deprecated The default value of the "manualCleanup" parameter will default
  19080. * to "false" in upcoming versions of Video.js
  19081. */
  19082. ;
  19083. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  19084. var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
  19085. if (this.featuresNativeTextTracks) {
  19086. this.el().appendChild(htmlTrackElement);
  19087. }
  19088. return htmlTrackElement;
  19089. }
  19090. /**
  19091. * Remove remote `TextTrack` from `TextTrackList` object
  19092. *
  19093. * @param {TextTrack} track
  19094. * `TextTrack` object to remove
  19095. */
  19096. ;
  19097. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  19098. _Tech.prototype.removeRemoteTextTrack.call(this, track);
  19099. if (this.featuresNativeTextTracks) {
  19100. var tracks = this.$$('track');
  19101. var i = tracks.length;
  19102. while (i--) {
  19103. if (track === tracks[i] || track === tracks[i].track) {
  19104. this.el().removeChild(tracks[i]);
  19105. }
  19106. }
  19107. }
  19108. }
  19109. /**
  19110. * Gets available media playback quality metrics as specified by the W3C's Media
  19111. * Playback Quality API.
  19112. *
  19113. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  19114. *
  19115. * @return {Object}
  19116. * An object with supported media playback quality metrics
  19117. */
  19118. ;
  19119. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  19120. if (typeof this.el().getVideoPlaybackQuality === 'function') {
  19121. return this.el().getVideoPlaybackQuality();
  19122. }
  19123. var videoPlaybackQuality = {};
  19124. if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
  19125. videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
  19126. videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
  19127. }
  19128. if (window$1.performance && typeof window$1.performance.now === 'function') {
  19129. videoPlaybackQuality.creationTime = window$1.performance.now();
  19130. } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
  19131. videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
  19132. }
  19133. return videoPlaybackQuality;
  19134. };
  19135. return Html5;
  19136. }(Tech);
  19137. /* HTML5 Support Testing ---------------------------------------------------- */
  19138. if (isReal()) {
  19139. /**
  19140. * Element for testing browser HTML5 media capabilities
  19141. *
  19142. * @type {Element}
  19143. * @constant
  19144. * @private
  19145. */
  19146. Html5.TEST_VID = document.createElement('video');
  19147. var track = document.createElement('track');
  19148. track.kind = 'captions';
  19149. track.srclang = 'en';
  19150. track.label = 'English';
  19151. Html5.TEST_VID.appendChild(track);
  19152. }
  19153. /**
  19154. * Check if HTML5 media is supported by this browser/device.
  19155. *
  19156. * @return {boolean}
  19157. * - True if HTML5 media is supported.
  19158. * - False if HTML5 media is not supported.
  19159. */
  19160. Html5.isSupported = function () {
  19161. // IE with no Media Player is a LIAR! (#984)
  19162. try {
  19163. Html5.TEST_VID.volume = 0.5;
  19164. } catch (e) {
  19165. return false;
  19166. }
  19167. return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
  19168. };
  19169. /**
  19170. * Check if the tech can support the given type
  19171. *
  19172. * @param {string} type
  19173. * The mimetype to check
  19174. * @return {string} 'probably', 'maybe', or '' (empty string)
  19175. */
  19176. Html5.canPlayType = function (type) {
  19177. return Html5.TEST_VID.canPlayType(type);
  19178. };
  19179. /**
  19180. * Check if the tech can support the given source
  19181. *
  19182. * @param {Object} srcObj
  19183. * The source object
  19184. * @param {Object} options
  19185. * The options passed to the tech
  19186. * @return {string} 'probably', 'maybe', or '' (empty string)
  19187. */
  19188. Html5.canPlaySource = function (srcObj, options) {
  19189. return Html5.canPlayType(srcObj.type);
  19190. };
  19191. /**
  19192. * Check if the volume can be changed in this browser/device.
  19193. * Volume cannot be changed in a lot of mobile devices.
  19194. * Specifically, it can't be changed from 1 on iOS.
  19195. *
  19196. * @return {boolean}
  19197. * - True if volume can be controlled
  19198. * - False otherwise
  19199. */
  19200. Html5.canControlVolume = function () {
  19201. // IE will error if Windows Media Player not installed #3315
  19202. try {
  19203. var volume = Html5.TEST_VID.volume;
  19204. Html5.TEST_VID.volume = volume / 2 + 0.1;
  19205. return volume !== Html5.TEST_VID.volume;
  19206. } catch (e) {
  19207. return false;
  19208. }
  19209. };
  19210. /**
  19211. * Check if the volume can be muted in this browser/device.
  19212. * Some devices, e.g. iOS, don't allow changing volume
  19213. * but permits muting/unmuting.
  19214. *
  19215. * @return {bolean}
  19216. * - True if volume can be muted
  19217. * - False otherwise
  19218. */
  19219. Html5.canMuteVolume = function () {
  19220. try {
  19221. var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
  19222. // work, so we want to set both property and attribute
  19223. Html5.TEST_VID.muted = !muted;
  19224. if (Html5.TEST_VID.muted) {
  19225. setAttribute(Html5.TEST_VID, 'muted', 'muted');
  19226. } else {
  19227. removeAttribute(Html5.TEST_VID, 'muted', 'muted');
  19228. }
  19229. return muted !== Html5.TEST_VID.muted;
  19230. } catch (e) {
  19231. return false;
  19232. }
  19233. };
  19234. /**
  19235. * Check if the playback rate can be changed in this browser/device.
  19236. *
  19237. * @return {boolean}
  19238. * - True if playback rate can be controlled
  19239. * - False otherwise
  19240. */
  19241. Html5.canControlPlaybackRate = function () {
  19242. // Playback rate API is implemented in Android Chrome, but doesn't do anything
  19243. // https://github.com/videojs/video.js/issues/3180
  19244. if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
  19245. return false;
  19246. } // IE will error if Windows Media Player not installed #3315
  19247. try {
  19248. var playbackRate = Html5.TEST_VID.playbackRate;
  19249. Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
  19250. return playbackRate !== Html5.TEST_VID.playbackRate;
  19251. } catch (e) {
  19252. return false;
  19253. }
  19254. };
  19255. /**
  19256. * Check if we can override a video/audio elements attributes, with
  19257. * Object.defineProperty.
  19258. *
  19259. * @return {boolean}
  19260. * - True if builtin attributes can be overridden
  19261. * - False otherwise
  19262. */
  19263. Html5.canOverrideAttributes = function () {
  19264. // if we cannot overwrite the src/innerHTML property, there is no support
  19265. // iOS 7 safari for instance cannot do this.
  19266. try {
  19267. var noop = function noop() {};
  19268. Object.defineProperty(document.createElement('video'), 'src', {
  19269. get: noop,
  19270. set: noop
  19271. });
  19272. Object.defineProperty(document.createElement('audio'), 'src', {
  19273. get: noop,
  19274. set: noop
  19275. });
  19276. Object.defineProperty(document.createElement('video'), 'innerHTML', {
  19277. get: noop,
  19278. set: noop
  19279. });
  19280. Object.defineProperty(document.createElement('audio'), 'innerHTML', {
  19281. get: noop,
  19282. set: noop
  19283. });
  19284. } catch (e) {
  19285. return false;
  19286. }
  19287. return true;
  19288. };
  19289. /**
  19290. * Check to see if native `TextTrack`s are supported by this browser/device.
  19291. *
  19292. * @return {boolean}
  19293. * - True if native `TextTrack`s are supported.
  19294. * - False otherwise
  19295. */
  19296. Html5.supportsNativeTextTracks = function () {
  19297. return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
  19298. };
  19299. /**
  19300. * Check to see if native `VideoTrack`s are supported by this browser/device
  19301. *
  19302. * @return {boolean}
  19303. * - True if native `VideoTrack`s are supported.
  19304. * - False otherwise
  19305. */
  19306. Html5.supportsNativeVideoTracks = function () {
  19307. return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
  19308. };
  19309. /**
  19310. * Check to see if native `AudioTrack`s are supported by this browser/device
  19311. *
  19312. * @return {boolean}
  19313. * - True if native `AudioTrack`s are supported.
  19314. * - False otherwise
  19315. */
  19316. Html5.supportsNativeAudioTracks = function () {
  19317. return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
  19318. };
  19319. /**
  19320. * An array of events available on the Html5 tech.
  19321. *
  19322. * @private
  19323. * @type {Array}
  19324. */
  19325. 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'];
  19326. /**
  19327. * Boolean indicating whether the `Tech` supports volume control.
  19328. *
  19329. * @type {boolean}
  19330. * @default {@link Html5.canControlVolume}
  19331. */
  19332. Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
  19333. /**
  19334. * Boolean indicating whether the `Tech` supports muting volume.
  19335. *
  19336. * @type {bolean}
  19337. * @default {@link Html5.canMuteVolume}
  19338. */
  19339. Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
  19340. /**
  19341. * Boolean indicating whether the `Tech` supports changing the speed at which the media
  19342. * plays. Examples:
  19343. * - Set player to play 2x (twice) as fast
  19344. * - Set player to play 0.5x (half) as fast
  19345. *
  19346. * @type {boolean}
  19347. * @default {@link Html5.canControlPlaybackRate}
  19348. */
  19349. Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
  19350. /**
  19351. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  19352. *
  19353. * @type {boolean}
  19354. * @default
  19355. */
  19356. Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
  19357. /**
  19358. * Boolean indicating whether the `HTML5` tech currently supports the media element
  19359. * moving in the DOM. iOS breaks if you move the media element, so this is set this to
  19360. * false there. Everywhere else this should be true.
  19361. *
  19362. * @type {boolean}
  19363. * @default
  19364. */
  19365. Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
  19366. // Is this true?
  19367. /**
  19368. * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
  19369. * when going into fullscreen.
  19370. *
  19371. * @type {boolean}
  19372. * @default
  19373. */
  19374. Html5.prototype.featuresFullscreenResize = true;
  19375. /**
  19376. * Boolean indicating whether the `HTML5` tech currently supports the progress event.
  19377. * If this is false, manual `progress` events will be triggered instead.
  19378. *
  19379. * @type {boolean}
  19380. * @default
  19381. */
  19382. Html5.prototype.featuresProgressEvents = true;
  19383. /**
  19384. * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
  19385. * If this is false, manual `timeupdate` events will be triggered instead.
  19386. *
  19387. * @default
  19388. */
  19389. Html5.prototype.featuresTimeupdateEvents = true;
  19390. /**
  19391. * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
  19392. *
  19393. * @type {boolean}
  19394. * @default {@link Html5.supportsNativeTextTracks}
  19395. */
  19396. Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
  19397. /**
  19398. * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
  19399. *
  19400. * @type {boolean}
  19401. * @default {@link Html5.supportsNativeVideoTracks}
  19402. */
  19403. Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
  19404. /**
  19405. * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
  19406. *
  19407. * @type {boolean}
  19408. * @default {@link Html5.supportsNativeAudioTracks}
  19409. */
  19410. Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); // HTML5 Feature detection and Device Fixes --------------------------------- //
  19411. var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
  19412. var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
  19413. Html5.patchCanPlayType = function () {
  19414. // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
  19415. // Firefox and Chrome report correctly
  19416. if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
  19417. Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
  19418. if (type && mpegurlRE.test(type)) {
  19419. return 'maybe';
  19420. }
  19421. return canPlayType.call(this, type);
  19422. };
  19423. }
  19424. };
  19425. Html5.unpatchCanPlayType = function () {
  19426. var r = Html5.TEST_VID.constructor.prototype.canPlayType;
  19427. Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  19428. return r;
  19429. }; // by default, patch the media element
  19430. Html5.patchCanPlayType();
  19431. Html5.disposeMediaElement = function (el) {
  19432. if (!el) {
  19433. return;
  19434. }
  19435. if (el.parentNode) {
  19436. el.parentNode.removeChild(el);
  19437. } // remove any child track or source nodes to prevent their loading
  19438. while (el.hasChildNodes()) {
  19439. el.removeChild(el.firstChild);
  19440. } // remove any src reference. not setting `src=''` because that causes a warning
  19441. // in firefox
  19442. el.removeAttribute('src'); // force the media element to update its loading state by calling load()
  19443. // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
  19444. if (typeof el.load === 'function') {
  19445. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  19446. (function () {
  19447. try {
  19448. el.load();
  19449. } catch (e) {// not supported
  19450. }
  19451. })();
  19452. }
  19453. };
  19454. Html5.resetMediaElement = function (el) {
  19455. if (!el) {
  19456. return;
  19457. }
  19458. var sources = el.querySelectorAll('source');
  19459. var i = sources.length;
  19460. while (i--) {
  19461. el.removeChild(sources[i]);
  19462. } // remove any src reference.
  19463. // not setting `src=''` because that throws an error
  19464. el.removeAttribute('src');
  19465. if (typeof el.load === 'function') {
  19466. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  19467. (function () {
  19468. try {
  19469. el.load();
  19470. } catch (e) {// satisfy linter
  19471. }
  19472. })();
  19473. }
  19474. };
  19475. /* Native HTML5 element property wrapping ----------------------------------- */
  19476. // Wrap native boolean attributes with getters that check both property and attribute
  19477. // The list is as followed:
  19478. // muted, defaultMuted, autoplay, controls, loop, playsinline
  19479. [
  19480. /**
  19481. * Get the value of `muted` from the media element. `muted` indicates
  19482. * that the volume for the media should be set to silent. This does not actually change
  19483. * the `volume` attribute.
  19484. *
  19485. * @method Html5#muted
  19486. * @return {boolean}
  19487. * - True if the value of `volume` should be ignored and the audio set to silent.
  19488. * - False if the value of `volume` should be used.
  19489. *
  19490. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  19491. */
  19492. 'muted',
  19493. /**
  19494. * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
  19495. * whether the media should start muted or not. Only changes the default state of the
  19496. * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
  19497. * current state.
  19498. *
  19499. * @method Html5#defaultMuted
  19500. * @return {boolean}
  19501. * - The value of `defaultMuted` from the media element.
  19502. * - True indicates that the media should start muted.
  19503. * - False indicates that the media should not start muted
  19504. *
  19505. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  19506. */
  19507. 'defaultMuted',
  19508. /**
  19509. * Get the value of `autoplay` from the media element. `autoplay` indicates
  19510. * that the media should start to play as soon as the page is ready.
  19511. *
  19512. * @method Html5#autoplay
  19513. * @return {boolean}
  19514. * - The value of `autoplay` from the media element.
  19515. * - True indicates that the media should start as soon as the page loads.
  19516. * - False indicates that the media should not start as soon as the page loads.
  19517. *
  19518. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  19519. */
  19520. 'autoplay',
  19521. /**
  19522. * Get the value of `controls` from the media element. `controls` indicates
  19523. * whether the native media controls should be shown or hidden.
  19524. *
  19525. * @method Html5#controls
  19526. * @return {boolean}
  19527. * - The value of `controls` from the media element.
  19528. * - True indicates that native controls should be showing.
  19529. * - False indicates that native controls should be hidden.
  19530. *
  19531. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
  19532. */
  19533. 'controls',
  19534. /**
  19535. * Get the value of `loop` from the media element. `loop` indicates
  19536. * that the media should return to the start of the media and continue playing once
  19537. * it reaches the end.
  19538. *
  19539. * @method Html5#loop
  19540. * @return {boolean}
  19541. * - The value of `loop` from the media element.
  19542. * - True indicates that playback should seek back to start once
  19543. * the end of a media is reached.
  19544. * - False indicates that playback should not loop back to the start when the
  19545. * end of the media is reached.
  19546. *
  19547. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  19548. */
  19549. 'loop',
  19550. /**
  19551. * Get the value of `playsinline` from the media element. `playsinline` indicates
  19552. * to the browser that non-fullscreen playback is preferred when fullscreen
  19553. * playback is the native default, such as in iOS Safari.
  19554. *
  19555. * @method Html5#playsinline
  19556. * @return {boolean}
  19557. * - The value of `playsinline` from the media element.
  19558. * - True indicates that the media should play inline.
  19559. * - False indicates that the media should not play inline.
  19560. *
  19561. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  19562. */
  19563. 'playsinline'].forEach(function (prop) {
  19564. Html5.prototype[prop] = function () {
  19565. return this.el_[prop] || this.el_.hasAttribute(prop);
  19566. };
  19567. }); // Wrap native boolean attributes with setters that set both property and attribute
  19568. // The list is as followed:
  19569. // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
  19570. // setControls is special-cased above
  19571. [
  19572. /**
  19573. * Set the value of `muted` on the media element. `muted` indicates that the current
  19574. * audio level should be silent.
  19575. *
  19576. * @method Html5#setMuted
  19577. * @param {boolean} muted
  19578. * - True if the audio should be set to silent
  19579. * - False otherwise
  19580. *
  19581. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  19582. */
  19583. 'muted',
  19584. /**
  19585. * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
  19586. * audio level should be silent, but will only effect the muted level on intial playback..
  19587. *
  19588. * @method Html5.prototype.setDefaultMuted
  19589. * @param {boolean} defaultMuted
  19590. * - True if the audio should be set to silent
  19591. * - False otherwise
  19592. *
  19593. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  19594. */
  19595. 'defaultMuted',
  19596. /**
  19597. * Set the value of `autoplay` on the media element. `autoplay` indicates
  19598. * that the media should start to play as soon as the page is ready.
  19599. *
  19600. * @method Html5#setAutoplay
  19601. * @param {boolean} autoplay
  19602. * - True indicates that the media should start as soon as the page loads.
  19603. * - False indicates that the media should not start as soon as the page loads.
  19604. *
  19605. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  19606. */
  19607. 'autoplay',
  19608. /**
  19609. * Set the value of `loop` on the media element. `loop` indicates
  19610. * that the media should return to the start of the media and continue playing once
  19611. * it reaches the end.
  19612. *
  19613. * @method Html5#setLoop
  19614. * @param {boolean} loop
  19615. * - True indicates that playback should seek back to start once
  19616. * the end of a media is reached.
  19617. * - False indicates that playback should not loop back to the start when the
  19618. * end of the media is reached.
  19619. *
  19620. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  19621. */
  19622. 'loop',
  19623. /**
  19624. * Set the value of `playsinline` from the media element. `playsinline` indicates
  19625. * to the browser that non-fullscreen playback is preferred when fullscreen
  19626. * playback is the native default, such as in iOS Safari.
  19627. *
  19628. * @method Html5#setPlaysinline
  19629. * @param {boolean} playsinline
  19630. * - True indicates that the media should play inline.
  19631. * - False indicates that the media should not play inline.
  19632. *
  19633. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  19634. */
  19635. 'playsinline'].forEach(function (prop) {
  19636. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  19637. this.el_[prop] = v;
  19638. if (v) {
  19639. this.el_.setAttribute(prop, prop);
  19640. } else {
  19641. this.el_.removeAttribute(prop);
  19642. }
  19643. };
  19644. }); // Wrap native properties with a getter
  19645. // The list is as followed
  19646. // paused, currentTime, buffered, volume, poster, preload, error, seeking
  19647. // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
  19648. // readyState, videoWidth, videoHeight
  19649. [
  19650. /**
  19651. * Get the value of `paused` from the media element. `paused` indicates whether the media element
  19652. * is currently paused or not.
  19653. *
  19654. * @method Html5#paused
  19655. * @return {boolean}
  19656. * The value of `paused` from the media element.
  19657. *
  19658. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
  19659. */
  19660. 'paused',
  19661. /**
  19662. * Get the value of `currentTime` from the media element. `currentTime` indicates
  19663. * the current second that the media is at in playback.
  19664. *
  19665. * @method Html5#currentTime
  19666. * @return {number}
  19667. * The value of `currentTime` from the media element.
  19668. *
  19669. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
  19670. */
  19671. 'currentTime',
  19672. /**
  19673. * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
  19674. * object that represents the parts of the media that are already downloaded and
  19675. * available for playback.
  19676. *
  19677. * @method Html5#buffered
  19678. * @return {TimeRange}
  19679. * The value of `buffered` from the media element.
  19680. *
  19681. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
  19682. */
  19683. 'buffered',
  19684. /**
  19685. * Get the value of `volume` from the media element. `volume` indicates
  19686. * the current playback volume of audio for a media. `volume` will be a value from 0
  19687. * (silent) to 1 (loudest and default).
  19688. *
  19689. * @method Html5#volume
  19690. * @return {number}
  19691. * The value of `volume` from the media element. Value will be between 0-1.
  19692. *
  19693. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  19694. */
  19695. 'volume',
  19696. /**
  19697. * Get the value of `poster` from the media element. `poster` indicates
  19698. * that the url of an image file that can/will be shown when no media data is available.
  19699. *
  19700. * @method Html5#poster
  19701. * @return {string}
  19702. * The value of `poster` from the media element. Value will be a url to an
  19703. * image.
  19704. *
  19705. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
  19706. */
  19707. 'poster',
  19708. /**
  19709. * Get the value of `preload` from the media element. `preload` indicates
  19710. * what should download before the media is interacted with. It can have the following
  19711. * values:
  19712. * - none: nothing should be downloaded
  19713. * - metadata: poster and the first few frames of the media may be downloaded to get
  19714. * media dimensions and other metadata
  19715. * - auto: allow the media and metadata for the media to be downloaded before
  19716. * interaction
  19717. *
  19718. * @method Html5#preload
  19719. * @return {string}
  19720. * The value of `preload` from the media element. Will be 'none', 'metadata',
  19721. * or 'auto'.
  19722. *
  19723. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  19724. */
  19725. 'preload',
  19726. /**
  19727. * Get the value of the `error` from the media element. `error` indicates any
  19728. * MediaError that may have occurred during playback. If error returns null there is no
  19729. * current error.
  19730. *
  19731. * @method Html5#error
  19732. * @return {MediaError|null}
  19733. * The value of `error` from the media element. Will be `MediaError` if there
  19734. * is a current error and null otherwise.
  19735. *
  19736. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
  19737. */
  19738. 'error',
  19739. /**
  19740. * Get the value of `seeking` from the media element. `seeking` indicates whether the
  19741. * media is currently seeking to a new position or not.
  19742. *
  19743. * @method Html5#seeking
  19744. * @return {boolean}
  19745. * - The value of `seeking` from the media element.
  19746. * - True indicates that the media is currently seeking to a new position.
  19747. * - False indicates that the media is not seeking to a new position at this time.
  19748. *
  19749. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
  19750. */
  19751. 'seeking',
  19752. /**
  19753. * Get the value of `seekable` from the media element. `seekable` returns a
  19754. * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
  19755. *
  19756. * @method Html5#seekable
  19757. * @return {TimeRange}
  19758. * The value of `seekable` from the media element. A `TimeRange` object
  19759. * indicating the current ranges of time that can be seeked to.
  19760. *
  19761. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
  19762. */
  19763. 'seekable',
  19764. /**
  19765. * Get the value of `ended` from the media element. `ended` indicates whether
  19766. * the media has reached the end or not.
  19767. *
  19768. * @method Html5#ended
  19769. * @return {boolean}
  19770. * - The value of `ended` from the media element.
  19771. * - True indicates that the media has ended.
  19772. * - False indicates that the media has not ended.
  19773. *
  19774. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
  19775. */
  19776. 'ended',
  19777. /**
  19778. * Get the value of `playbackRate` from the media element. `playbackRate` indicates
  19779. * the rate at which the media is currently playing back. Examples:
  19780. * - if playbackRate is set to 2, media will play twice as fast.
  19781. * - if playbackRate is set to 0.5, media will play half as fast.
  19782. *
  19783. * @method Html5#playbackRate
  19784. * @return {number}
  19785. * The value of `playbackRate` from the media element. A number indicating
  19786. * the current playback speed of the media, where 1 is normal speed.
  19787. *
  19788. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  19789. */
  19790. 'playbackRate',
  19791. /**
  19792. * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
  19793. * the rate at which the media is currently playing back. This value will not indicate the current
  19794. * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
  19795. *
  19796. * Examples:
  19797. * - if defaultPlaybackRate is set to 2, media will play twice as fast.
  19798. * - if defaultPlaybackRate is set to 0.5, media will play half as fast.
  19799. *
  19800. * @method Html5.prototype.defaultPlaybackRate
  19801. * @return {number}
  19802. * The value of `defaultPlaybackRate` from the media element. A number indicating
  19803. * the current playback speed of the media, where 1 is normal speed.
  19804. *
  19805. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  19806. */
  19807. 'defaultPlaybackRate',
  19808. /**
  19809. * Get the value of `played` from the media element. `played` returns a `TimeRange`
  19810. * object representing points in the media timeline that have been played.
  19811. *
  19812. * @method Html5#played
  19813. * @return {TimeRange}
  19814. * The value of `played` from the media element. A `TimeRange` object indicating
  19815. * the ranges of time that have been played.
  19816. *
  19817. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
  19818. */
  19819. 'played',
  19820. /**
  19821. * Get the value of `networkState` from the media element. `networkState` indicates
  19822. * the current network state. It returns an enumeration from the following list:
  19823. * - 0: NETWORK_EMPTY
  19824. * - 1: NETWORK_IDLE
  19825. * - 2: NETWORK_LOADING
  19826. * - 3: NETWORK_NO_SOURCE
  19827. *
  19828. * @method Html5#networkState
  19829. * @return {number}
  19830. * The value of `networkState` from the media element. This will be a number
  19831. * from the list in the description.
  19832. *
  19833. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
  19834. */
  19835. 'networkState',
  19836. /**
  19837. * Get the value of `readyState` from the media element. `readyState` indicates
  19838. * the current state of the media element. It returns an enumeration from the
  19839. * following list:
  19840. * - 0: HAVE_NOTHING
  19841. * - 1: HAVE_METADATA
  19842. * - 2: HAVE_CURRENT_DATA
  19843. * - 3: HAVE_FUTURE_DATA
  19844. * - 4: HAVE_ENOUGH_DATA
  19845. *
  19846. * @method Html5#readyState
  19847. * @return {number}
  19848. * The value of `readyState` from the media element. This will be a number
  19849. * from the list in the description.
  19850. *
  19851. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
  19852. */
  19853. 'readyState',
  19854. /**
  19855. * Get the value of `videoWidth` from the video element. `videoWidth` indicates
  19856. * the current width of the video in css pixels.
  19857. *
  19858. * @method Html5#videoWidth
  19859. * @return {number}
  19860. * The value of `videoWidth` from the video element. This will be a number
  19861. * in css pixels.
  19862. *
  19863. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  19864. */
  19865. 'videoWidth',
  19866. /**
  19867. * Get the value of `videoHeight` from the video element. `videoHeight` indicates
  19868. * the current height of the video in css pixels.
  19869. *
  19870. * @method Html5#videoHeight
  19871. * @return {number}
  19872. * The value of `videoHeight` from the video element. This will be a number
  19873. * in css pixels.
  19874. *
  19875. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  19876. */
  19877. 'videoHeight'].forEach(function (prop) {
  19878. Html5.prototype[prop] = function () {
  19879. return this.el_[prop];
  19880. };
  19881. }); // Wrap native properties with a setter in this format:
  19882. // set + toTitleCase(name)
  19883. // The list is as follows:
  19884. // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
  19885. [
  19886. /**
  19887. * Set the value of `volume` on the media element. `volume` indicates the current
  19888. * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
  19889. * so on.
  19890. *
  19891. * @method Html5#setVolume
  19892. * @param {number} percentAsDecimal
  19893. * The volume percent as a decimal. Valid range is from 0-1.
  19894. *
  19895. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  19896. */
  19897. 'volume',
  19898. /**
  19899. * Set the value of `src` on the media element. `src` indicates the current
  19900. * {@link Tech~SourceObject} for the media.
  19901. *
  19902. * @method Html5#setSrc
  19903. * @param {Tech~SourceObject} src
  19904. * The source object to set as the current source.
  19905. *
  19906. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
  19907. */
  19908. 'src',
  19909. /**
  19910. * Set the value of `poster` on the media element. `poster` is the url to
  19911. * an image file that can/will be shown when no media data is available.
  19912. *
  19913. * @method Html5#setPoster
  19914. * @param {string} poster
  19915. * The url to an image that should be used as the `poster` for the media
  19916. * element.
  19917. *
  19918. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
  19919. */
  19920. 'poster',
  19921. /**
  19922. * Set the value of `preload` on the media element. `preload` indicates
  19923. * what should download before the media is interacted with. It can have the following
  19924. * values:
  19925. * - none: nothing should be downloaded
  19926. * - metadata: poster and the first few frames of the media may be downloaded to get
  19927. * media dimensions and other metadata
  19928. * - auto: allow the media and metadata for the media to be downloaded before
  19929. * interaction
  19930. *
  19931. * @method Html5#setPreload
  19932. * @param {string} preload
  19933. * The value of `preload` to set on the media element. Must be 'none', 'metadata',
  19934. * or 'auto'.
  19935. *
  19936. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  19937. */
  19938. 'preload',
  19939. /**
  19940. * Set the value of `playbackRate` on the media element. `playbackRate` indicates
  19941. * the rate at which the media should play back. Examples:
  19942. * - if playbackRate is set to 2, media will play twice as fast.
  19943. * - if playbackRate is set to 0.5, media will play half as fast.
  19944. *
  19945. * @method Html5#setPlaybackRate
  19946. * @return {number}
  19947. * The value of `playbackRate` from the media element. A number indicating
  19948. * the current playback speed of the media, where 1 is normal speed.
  19949. *
  19950. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  19951. */
  19952. 'playbackRate',
  19953. /**
  19954. * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
  19955. * the rate at which the media should play back upon initial startup. Changing this value
  19956. * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
  19957. *
  19958. * Example Values:
  19959. * - if playbackRate is set to 2, media will play twice as fast.
  19960. * - if playbackRate is set to 0.5, media will play half as fast.
  19961. *
  19962. * @method Html5.prototype.setDefaultPlaybackRate
  19963. * @return {number}
  19964. * The value of `defaultPlaybackRate` from the media element. A number indicating
  19965. * the current playback speed of the media, where 1 is normal speed.
  19966. *
  19967. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
  19968. */
  19969. 'defaultPlaybackRate'].forEach(function (prop) {
  19970. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  19971. this.el_[prop] = v;
  19972. };
  19973. }); // wrap native functions with a function
  19974. // The list is as follows:
  19975. // pause, load, play
  19976. [
  19977. /**
  19978. * A wrapper around the media elements `pause` function. This will call the `HTML5`
  19979. * media elements `pause` function.
  19980. *
  19981. * @method Html5#pause
  19982. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
  19983. */
  19984. 'pause',
  19985. /**
  19986. * A wrapper around the media elements `load` function. This will call the `HTML5`s
  19987. * media element `load` function.
  19988. *
  19989. * @method Html5#load
  19990. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
  19991. */
  19992. 'load',
  19993. /**
  19994. * A wrapper around the media elements `play` function. This will call the `HTML5`s
  19995. * media element `play` function.
  19996. *
  19997. * @method Html5#play
  19998. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
  19999. */
  20000. 'play'].forEach(function (prop) {
  20001. Html5.prototype[prop] = function () {
  20002. return this.el_[prop]();
  20003. };
  20004. });
  20005. Tech.withSourceHandlers(Html5);
  20006. /**
  20007. * Native source handler for Html5, simply passes the source to the media element.
  20008. *
  20009. * @property {Tech~SourceObject} source
  20010. * The source object
  20011. *
  20012. * @property {Html5} tech
  20013. * The instance of the HTML5 tech.
  20014. */
  20015. Html5.nativeSourceHandler = {};
  20016. /**
  20017. * Check if the media element can play the given mime type.
  20018. *
  20019. * @param {string} type
  20020. * The mimetype to check
  20021. *
  20022. * @return {string}
  20023. * 'probably', 'maybe', or '' (empty string)
  20024. */
  20025. Html5.nativeSourceHandler.canPlayType = function (type) {
  20026. // IE without MediaPlayer throws an error (#519)
  20027. try {
  20028. return Html5.TEST_VID.canPlayType(type);
  20029. } catch (e) {
  20030. return '';
  20031. }
  20032. };
  20033. /**
  20034. * Check if the media element can handle a source natively.
  20035. *
  20036. * @param {Tech~SourceObject} source
  20037. * The source object
  20038. *
  20039. * @param {Object} [options]
  20040. * Options to be passed to the tech.
  20041. *
  20042. * @return {string}
  20043. * 'probably', 'maybe', or '' (empty string).
  20044. */
  20045. Html5.nativeSourceHandler.canHandleSource = function (source, options) {
  20046. // If a type was provided we should rely on that
  20047. if (source.type) {
  20048. return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
  20049. } else if (source.src) {
  20050. var ext = getFileExtension(source.src);
  20051. return Html5.nativeSourceHandler.canPlayType("video/" + ext);
  20052. }
  20053. return '';
  20054. };
  20055. /**
  20056. * Pass the source to the native media element.
  20057. *
  20058. * @param {Tech~SourceObject} source
  20059. * The source object
  20060. *
  20061. * @param {Html5} tech
  20062. * The instance of the Html5 tech
  20063. *
  20064. * @param {Object} [options]
  20065. * The options to pass to the source
  20066. */
  20067. Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
  20068. tech.setSrc(source.src);
  20069. };
  20070. /**
  20071. * A noop for the native dispose function, as cleanup is not needed.
  20072. */
  20073. Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
  20074. Html5.registerSourceHandler(Html5.nativeSourceHandler);
  20075. Tech.registerTech('Html5', Html5);
  20076. function _templateObject$2() {
  20077. 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 "]);
  20078. _templateObject$2 = function _templateObject() {
  20079. return data;
  20080. };
  20081. return data;
  20082. }
  20083. // on the player when they happen
  20084. var TECH_EVENTS_RETRIGGER = [
  20085. /**
  20086. * Fired while the user agent is downloading media data.
  20087. *
  20088. * @event Player#progress
  20089. * @type {EventTarget~Event}
  20090. */
  20091. /**
  20092. * Retrigger the `progress` event that was triggered by the {@link Tech}.
  20093. *
  20094. * @private
  20095. * @method Player#handleTechProgress_
  20096. * @fires Player#progress
  20097. * @listens Tech#progress
  20098. */
  20099. 'progress',
  20100. /**
  20101. * Fires when the loading of an audio/video is aborted.
  20102. *
  20103. * @event Player#abort
  20104. * @type {EventTarget~Event}
  20105. */
  20106. /**
  20107. * Retrigger the `abort` event that was triggered by the {@link Tech}.
  20108. *
  20109. * @private
  20110. * @method Player#handleTechAbort_
  20111. * @fires Player#abort
  20112. * @listens Tech#abort
  20113. */
  20114. 'abort',
  20115. /**
  20116. * Fires when the browser is intentionally not getting media data.
  20117. *
  20118. * @event Player#suspend
  20119. * @type {EventTarget~Event}
  20120. */
  20121. /**
  20122. * Retrigger the `suspend` event that was triggered by the {@link Tech}.
  20123. *
  20124. * @private
  20125. * @method Player#handleTechSuspend_
  20126. * @fires Player#suspend
  20127. * @listens Tech#suspend
  20128. */
  20129. 'suspend',
  20130. /**
  20131. * Fires when the current playlist is empty.
  20132. *
  20133. * @event Player#emptied
  20134. * @type {EventTarget~Event}
  20135. */
  20136. /**
  20137. * Retrigger the `emptied` event that was triggered by the {@link Tech}.
  20138. *
  20139. * @private
  20140. * @method Player#handleTechEmptied_
  20141. * @fires Player#emptied
  20142. * @listens Tech#emptied
  20143. */
  20144. 'emptied',
  20145. /**
  20146. * Fires when the browser is trying to get media data, but data is not available.
  20147. *
  20148. * @event Player#stalled
  20149. * @type {EventTarget~Event}
  20150. */
  20151. /**
  20152. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  20153. *
  20154. * @private
  20155. * @method Player#handleTechStalled_
  20156. * @fires Player#stalled
  20157. * @listens Tech#stalled
  20158. */
  20159. 'stalled',
  20160. /**
  20161. * Fires when the browser has loaded meta data for the audio/video.
  20162. *
  20163. * @event Player#loadedmetadata
  20164. * @type {EventTarget~Event}
  20165. */
  20166. /**
  20167. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  20168. *
  20169. * @private
  20170. * @method Player#handleTechLoadedmetadata_
  20171. * @fires Player#loadedmetadata
  20172. * @listens Tech#loadedmetadata
  20173. */
  20174. 'loadedmetadata',
  20175. /**
  20176. * Fires when the browser has loaded the current frame of the audio/video.
  20177. *
  20178. * @event Player#loadeddata
  20179. * @type {event}
  20180. */
  20181. /**
  20182. * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
  20183. *
  20184. * @private
  20185. * @method Player#handleTechLoaddeddata_
  20186. * @fires Player#loadeddata
  20187. * @listens Tech#loadeddata
  20188. */
  20189. 'loadeddata',
  20190. /**
  20191. * Fires when the current playback position has changed.
  20192. *
  20193. * @event Player#timeupdate
  20194. * @type {event}
  20195. */
  20196. /**
  20197. * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
  20198. *
  20199. * @private
  20200. * @method Player#handleTechTimeUpdate_
  20201. * @fires Player#timeupdate
  20202. * @listens Tech#timeupdate
  20203. */
  20204. 'timeupdate',
  20205. /**
  20206. * Fires when the video's intrinsic dimensions change
  20207. *
  20208. * @event Player#resize
  20209. * @type {event}
  20210. */
  20211. /**
  20212. * Retrigger the `resize` event that was triggered by the {@link Tech}.
  20213. *
  20214. * @private
  20215. * @method Player#handleTechResize_
  20216. * @fires Player#resize
  20217. * @listens Tech#resize
  20218. */
  20219. 'resize',
  20220. /**
  20221. * Fires when the volume has been changed
  20222. *
  20223. * @event Player#volumechange
  20224. * @type {event}
  20225. */
  20226. /**
  20227. * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
  20228. *
  20229. * @private
  20230. * @method Player#handleTechVolumechange_
  20231. * @fires Player#volumechange
  20232. * @listens Tech#volumechange
  20233. */
  20234. 'volumechange',
  20235. /**
  20236. * Fires when the text track has been changed
  20237. *
  20238. * @event Player#texttrackchange
  20239. * @type {event}
  20240. */
  20241. /**
  20242. * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
  20243. *
  20244. * @private
  20245. * @method Player#handleTechTexttrackchange_
  20246. * @fires Player#texttrackchange
  20247. * @listens Tech#texttrackchange
  20248. */
  20249. 'texttrackchange']; // events to queue when playback rate is zero
  20250. // this is a hash for the sole purpose of mapping non-camel-cased event names
  20251. // to camel-cased function names
  20252. var TECH_EVENTS_QUEUE = {
  20253. canplay: 'CanPlay',
  20254. canplaythrough: 'CanPlayThrough',
  20255. playing: 'Playing',
  20256. seeked: 'Seeked'
  20257. };
  20258. var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
  20259. var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
  20260. // grep: vjs-layout-x-small
  20261. // grep: vjs-layout-small
  20262. // grep: vjs-layout-medium
  20263. // grep: vjs-layout-large
  20264. // grep: vjs-layout-x-large
  20265. // grep: vjs-layout-huge
  20266. BREAKPOINT_ORDER.forEach(function (k) {
  20267. var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
  20268. BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
  20269. });
  20270. var DEFAULT_BREAKPOINTS = {
  20271. tiny: 210,
  20272. xsmall: 320,
  20273. small: 425,
  20274. medium: 768,
  20275. large: 1440,
  20276. xlarge: 2560,
  20277. huge: Infinity
  20278. };
  20279. /**
  20280. * An instance of the `Player` class is created when any of the Video.js setup methods
  20281. * are used to initialize a video.
  20282. *
  20283. * After an instance has been created it can be accessed globally in two ways:
  20284. * 1. By calling `videojs('example_video_1');`
  20285. * 2. By using it directly via `videojs.players.example_video_1;`
  20286. *
  20287. * @extends Component
  20288. */
  20289. var Player =
  20290. /*#__PURE__*/
  20291. function (_Component) {
  20292. _inheritsLoose(Player, _Component);
  20293. /**
  20294. * Create an instance of this class.
  20295. *
  20296. * @param {Element} tag
  20297. * The original video DOM element used for configuring options.
  20298. *
  20299. * @param {Object} [options]
  20300. * Object of option names and values.
  20301. *
  20302. * @param {Component~ReadyCallback} [ready]
  20303. * Ready callback function.
  20304. */
  20305. function Player(tag, options, ready) {
  20306. var _this;
  20307. // Make sure tag ID exists
  20308. tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
  20309. // The options argument overrides options set in the video tag
  20310. // which overrides globally set options.
  20311. // This latter part coincides with the load order
  20312. // (tag must exist before Player)
  20313. options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
  20314. // player properties first, and can't use `this` before `super()`
  20315. options.initChildren = false; // Same with creating the element
  20316. options.createEl = false; // don't auto mixin the evented mixin
  20317. options.evented = false; // we don't want the player to report touch activity on itself
  20318. // see enableTouchActivity in Component
  20319. options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
  20320. if (!options.language) {
  20321. if (typeof tag.closest === 'function') {
  20322. var closest = tag.closest('[lang]');
  20323. if (closest && closest.getAttribute) {
  20324. options.language = closest.getAttribute('lang');
  20325. }
  20326. } else {
  20327. var element = tag;
  20328. while (element && element.nodeType === 1) {
  20329. if (getAttributes(element).hasOwnProperty('lang')) {
  20330. options.language = element.getAttribute('lang');
  20331. break;
  20332. }
  20333. element = element.parentNode;
  20334. }
  20335. }
  20336. } // Run base component initializing with new options
  20337. _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
  20338. _this.boundDocumentFullscreenChange_ = bind(_assertThisInitialized(_this), _this.documentFullscreenChange_);
  20339. _this.boundFullWindowOnEscKey_ = bind(_assertThisInitialized(_this), _this.fullWindowOnEscKey); // create logger
  20340. _this.log = createLogger$1(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests
  20341. _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster
  20342. _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
  20343. // and a seek is happening
  20344. _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
  20345. _this.isReady_ = false; // Init state hasStarted_
  20346. _this.hasStarted_ = false; // Init state userActive_
  20347. _this.userActive_ = false; // if the global option object was accidentally blown away by
  20348. // someone, bail early with an informative error
  20349. if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
  20350. throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
  20351. } // Store the original tag used to set options
  20352. _this.tag = tag; // Store the tag attributes used to restore html5 element
  20353. _this.tagAttributes = tag && getAttributes(tag); // Update current language
  20354. _this.language(_this.options_.language); // Update Supported Languages
  20355. if (options.languages) {
  20356. // Normalise player option languages to lowercase
  20357. var languagesToLower = {};
  20358. Object.getOwnPropertyNames(options.languages).forEach(function (name) {
  20359. languagesToLower[name.toLowerCase()] = options.languages[name];
  20360. });
  20361. _this.languages_ = languagesToLower;
  20362. } else {
  20363. _this.languages_ = Player.prototype.options_.languages;
  20364. }
  20365. _this.resetCache_(); // Set poster
  20366. _this.poster_ = options.poster || ''; // Set controls
  20367. _this.controls_ = !!options.controls; // Original tag settings stored in options
  20368. // now remove immediately so native controls don't flash.
  20369. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  20370. tag.controls = false;
  20371. tag.removeAttribute('controls');
  20372. _this.changingSrc_ = false;
  20373. _this.playCallbacks_ = [];
  20374. _this.playTerminatedQueue_ = []; // the attribute overrides the option
  20375. if (tag.hasAttribute('autoplay')) {
  20376. _this.autoplay(true);
  20377. } else {
  20378. // otherwise use the setter to validate and
  20379. // set the correct value.
  20380. _this.autoplay(_this.options_.autoplay);
  20381. } // check plugins
  20382. if (options.plugins) {
  20383. Object.keys(options.plugins).forEach(function (name) {
  20384. if (typeof _this[name] !== 'function') {
  20385. throw new Error("plugin \"" + name + "\" does not exist");
  20386. }
  20387. });
  20388. }
  20389. /*
  20390. * Store the internal state of scrubbing
  20391. *
  20392. * @private
  20393. * @return {Boolean} True if the user is scrubbing
  20394. */
  20395. _this.scrubbing_ = false;
  20396. _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
  20397. evented(_assertThisInitialized(_this), {
  20398. eventBusKey: 'el_'
  20399. });
  20400. if (_this.fluid_) {
  20401. _this.on('playerreset', _this.updateStyleEl_);
  20402. } // We also want to pass the original player options to each component and plugin
  20403. // as well so they don't need to reach back into the player for options later.
  20404. // We also need to do another copy of this.options_ so we don't end up with
  20405. // an infinite loop.
  20406. var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins
  20407. if (options.plugins) {
  20408. Object.keys(options.plugins).forEach(function (name) {
  20409. _this[name](options.plugins[name]);
  20410. });
  20411. }
  20412. _this.options_.playerOptions = playerOptionsCopy;
  20413. _this.middleware_ = [];
  20414. _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
  20415. _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
  20416. // set because the element doesn't exist yet.
  20417. if (_this.controls()) {
  20418. _this.addClass('vjs-controls-enabled');
  20419. } else {
  20420. _this.addClass('vjs-controls-disabled');
  20421. } // Set ARIA label and region role depending on player type
  20422. _this.el_.setAttribute('role', 'region');
  20423. if (_this.isAudio()) {
  20424. _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
  20425. } else {
  20426. _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
  20427. }
  20428. if (_this.isAudio()) {
  20429. _this.addClass('vjs-audio');
  20430. }
  20431. if (_this.flexNotSupported_()) {
  20432. _this.addClass('vjs-no-flex');
  20433. } // TODO: Make this smarter. Toggle user state between touching/mousing
  20434. // using events, since devices can have both touch and mouse events.
  20435. // TODO: Make this check be performed again when the window switches between monitors
  20436. // (See https://github.com/videojs/video.js/issues/5683)
  20437. if (TOUCH_ENABLED) {
  20438. _this.addClass('vjs-touch-enabled');
  20439. } // iOS Safari has broken hover handling
  20440. if (!IS_IOS) {
  20441. _this.addClass('vjs-workinghover');
  20442. } // Make player easily findable by ID
  20443. Player.players[_this.id_] = _assertThisInitialized(_this); // Add a major version class to aid css in plugins
  20444. var majorVersion = version.split('.')[0];
  20445. _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
  20446. // like the control bar show themselves if needed
  20447. _this.userActive(true);
  20448. _this.reportUserActivity();
  20449. _this.one('play', _this.listenForUserActivity_);
  20450. _this.on('stageclick', _this.handleStageClick_);
  20451. _this.on('keydown', _this.handleKeyDown);
  20452. _this.breakpoints(_this.options_.breakpoints);
  20453. _this.responsive(_this.options_.responsive);
  20454. return _this;
  20455. }
  20456. /**
  20457. * Destroys the video player and does any necessary cleanup.
  20458. *
  20459. * This is especially helpful if you are dynamically adding and removing videos
  20460. * to/from the DOM.
  20461. *
  20462. * @fires Player#dispose
  20463. */
  20464. var _proto = Player.prototype;
  20465. _proto.dispose = function dispose() {
  20466. var _this2 = this;
  20467. /**
  20468. * Called when the player is being disposed of.
  20469. *
  20470. * @event Player#dispose
  20471. * @type {EventTarget~Event}
  20472. */
  20473. this.trigger('dispose'); // prevent dispose from being called twice
  20474. this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
  20475. off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
  20476. off(document, 'keydown', this.boundFullWindowOnEscKey_);
  20477. if (this.styleEl_ && this.styleEl_.parentNode) {
  20478. this.styleEl_.parentNode.removeChild(this.styleEl_);
  20479. this.styleEl_ = null;
  20480. } // Kill reference to this player
  20481. Player.players[this.id_] = null;
  20482. if (this.tag && this.tag.player) {
  20483. this.tag.player = null;
  20484. }
  20485. if (this.el_ && this.el_.player) {
  20486. this.el_.player = null;
  20487. }
  20488. if (this.tech_) {
  20489. this.tech_.dispose();
  20490. this.isPosterFromTech_ = false;
  20491. this.poster_ = '';
  20492. }
  20493. if (this.playerElIngest_) {
  20494. this.playerElIngest_ = null;
  20495. }
  20496. if (this.tag) {
  20497. this.tag = null;
  20498. }
  20499. clearCacheForPlayer(this); // remove all event handlers for track lists
  20500. // all tracks and track listeners are removed on
  20501. // tech dispose
  20502. ALL.names.forEach(function (name) {
  20503. var props = ALL[name];
  20504. var list = _this2[props.getterName](); // if it is not a native list
  20505. // we have to manually remove event listeners
  20506. if (list && list.off) {
  20507. list.off();
  20508. }
  20509. }); // the actual .el_ is removed here
  20510. _Component.prototype.dispose.call(this);
  20511. }
  20512. /**
  20513. * Create the `Player`'s DOM element.
  20514. *
  20515. * @return {Element}
  20516. * The DOM element that gets created.
  20517. */
  20518. ;
  20519. _proto.createEl = function createEl() {
  20520. var tag = this.tag;
  20521. var el;
  20522. var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
  20523. var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
  20524. if (playerElIngest) {
  20525. el = this.el_ = tag.parentNode;
  20526. } else if (!divEmbed) {
  20527. el = this.el_ = _Component.prototype.createEl.call(this, 'div');
  20528. } // Copy over all the attributes from the tag, including ID and class
  20529. // ID will now reference player box, not the video tag
  20530. var attrs = getAttributes(tag);
  20531. if (divEmbed) {
  20532. el = this.el_ = tag;
  20533. tag = this.tag = document.createElement('video');
  20534. while (el.children.length) {
  20535. tag.appendChild(el.firstChild);
  20536. }
  20537. if (!hasClass(el, 'video-js')) {
  20538. addClass(el, 'video-js');
  20539. }
  20540. el.appendChild(tag);
  20541. playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
  20542. // to our new `video` element. This will move things like
  20543. // `src` or `controls` that were set via js before the player
  20544. // was initialized.
  20545. Object.keys(el).forEach(function (k) {
  20546. tag[k] = el[k];
  20547. });
  20548. } // set tabindex to -1 to remove the video element from the focus order
  20549. tag.setAttribute('tabindex', '-1');
  20550. attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and
  20551. // for the same issue with Chrome (on Windows) with JAWS.
  20552. // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
  20553. // Note that we can't detect if JAWS is being used, but this ARIA attribute
  20554. // doesn't change behavior of IE11 or Chrome if JAWS is not being used
  20555. if (IE_VERSION || IS_CHROME && IS_WINDOWS) {
  20556. tag.setAttribute('role', 'application');
  20557. attrs.role = 'application';
  20558. } // Remove width/height attrs from tag so CSS can make it 100% width/height
  20559. tag.removeAttribute('width');
  20560. tag.removeAttribute('height');
  20561. if ('width' in attrs) {
  20562. delete attrs.width;
  20563. }
  20564. if ('height' in attrs) {
  20565. delete attrs.height;
  20566. }
  20567. Object.getOwnPropertyNames(attrs).forEach(function (attr) {
  20568. // don't copy over the class attribute to the player element when we're in a div embed
  20569. // the class is already set up properly in the divEmbed case
  20570. // and we want to make sure that the `video-js` class doesn't get lost
  20571. if (!(divEmbed && attr === 'class')) {
  20572. el.setAttribute(attr, attrs[attr]);
  20573. }
  20574. if (divEmbed) {
  20575. tag.setAttribute(attr, attrs[attr]);
  20576. }
  20577. }); // Update tag id/class for use as HTML5 playback tech
  20578. // Might think we should do this after embedding in container so .vjs-tech class
  20579. // doesn't flash 100% width/height, but class only applies with .video-js parent
  20580. tag.playerId = tag.id;
  20581. tag.id += '_html5_api';
  20582. tag.className = 'vjs-tech'; // Make player findable on elements
  20583. tag.player = el.player = this; // Default state of video is paused
  20584. this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
  20585. // of the player in a way that's still overrideable by CSS, just like the
  20586. // video element
  20587. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
  20588. this.styleEl_ = createStyleElement('vjs-styles-dimensions');
  20589. var defaultsStyleEl = $('.vjs-styles-defaults');
  20590. var head = $('head');
  20591. head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
  20592. }
  20593. this.fill_ = false;
  20594. this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
  20595. this.width(this.options_.width);
  20596. this.height(this.options_.height);
  20597. this.fill(this.options_.fill);
  20598. this.fluid(this.options_.fluid);
  20599. this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag,
  20600. // because IE doesn't hide them completely from screen readers.
  20601. var links = tag.getElementsByTagName('a');
  20602. for (var i = 0; i < links.length; i++) {
  20603. var linkEl = links.item(i);
  20604. addClass(linkEl, 'vjs-hidden');
  20605. linkEl.setAttribute('hidden', 'hidden');
  20606. } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
  20607. // keep track of the original for later so we can know if the source originally failed
  20608. tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
  20609. if (tag.parentNode && !playerElIngest) {
  20610. tag.parentNode.insertBefore(el, tag);
  20611. } // insert the tag as the first child of the player element
  20612. // then manually add it to the children array so that this.addChild
  20613. // will work properly for other components
  20614. //
  20615. // Breaks iPhone, fixed in HTML5 setup.
  20616. prependTo(tag, el);
  20617. this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
  20618. // if it's been set to something different to the doc
  20619. this.el_.setAttribute('lang', this.language_);
  20620. this.el_ = el;
  20621. return el;
  20622. }
  20623. /**
  20624. * A getter/setter for the `Player`'s width. Returns the player's configured value.
  20625. * To get the current width use `currentWidth()`.
  20626. *
  20627. * @param {number} [value]
  20628. * The value to set the `Player`'s width to.
  20629. *
  20630. * @return {number}
  20631. * The current width of the `Player` when getting.
  20632. */
  20633. ;
  20634. _proto.width = function width(value) {
  20635. return this.dimension('width', value);
  20636. }
  20637. /**
  20638. * A getter/setter for the `Player`'s height. Returns the player's configured value.
  20639. * To get the current height use `currentheight()`.
  20640. *
  20641. * @param {number} [value]
  20642. * The value to set the `Player`'s heigth to.
  20643. *
  20644. * @return {number}
  20645. * The current height of the `Player` when getting.
  20646. */
  20647. ;
  20648. _proto.height = function height(value) {
  20649. return this.dimension('height', value);
  20650. }
  20651. /**
  20652. * A getter/setter for the `Player`'s width & height.
  20653. *
  20654. * @param {string} dimension
  20655. * This string can be:
  20656. * - 'width'
  20657. * - 'height'
  20658. *
  20659. * @param {number} [value]
  20660. * Value for dimension specified in the first argument.
  20661. *
  20662. * @return {number}
  20663. * The dimension arguments value when getting (width/height).
  20664. */
  20665. ;
  20666. _proto.dimension = function dimension(_dimension, value) {
  20667. var privDimension = _dimension + '_';
  20668. if (value === undefined) {
  20669. return this[privDimension] || 0;
  20670. }
  20671. if (value === '') {
  20672. // If an empty string is given, reset the dimension to be automatic
  20673. this[privDimension] = undefined;
  20674. this.updateStyleEl_();
  20675. return;
  20676. }
  20677. var parsedVal = parseFloat(value);
  20678. if (isNaN(parsedVal)) {
  20679. log.error("Improper value \"" + value + "\" supplied for for " + _dimension);
  20680. return;
  20681. }
  20682. this[privDimension] = parsedVal;
  20683. this.updateStyleEl_();
  20684. }
  20685. /**
  20686. * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
  20687. *
  20688. * Turning this on will turn off fill mode.
  20689. *
  20690. * @param {boolean} [bool]
  20691. * - A value of true adds the class.
  20692. * - A value of false removes the class.
  20693. * - No value will be a getter.
  20694. *
  20695. * @return {boolean|undefined}
  20696. * - The value of fluid when getting.
  20697. * - `undefined` when setting.
  20698. */
  20699. ;
  20700. _proto.fluid = function fluid(bool) {
  20701. if (bool === undefined) {
  20702. return !!this.fluid_;
  20703. }
  20704. this.fluid_ = !!bool;
  20705. if (isEvented(this)) {
  20706. this.off('playerreset', this.updateStyleEl_);
  20707. }
  20708. if (bool) {
  20709. this.addClass('vjs-fluid');
  20710. this.fill(false);
  20711. addEventedCallback(function () {
  20712. this.on('playerreset', this.updateStyleEl_);
  20713. });
  20714. } else {
  20715. this.removeClass('vjs-fluid');
  20716. }
  20717. this.updateStyleEl_();
  20718. }
  20719. /**
  20720. * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
  20721. *
  20722. * Turning this on will turn off fluid mode.
  20723. *
  20724. * @param {boolean} [bool]
  20725. * - A value of true adds the class.
  20726. * - A value of false removes the class.
  20727. * - No value will be a getter.
  20728. *
  20729. * @return {boolean|undefined}
  20730. * - The value of fluid when getting.
  20731. * - `undefined` when setting.
  20732. */
  20733. ;
  20734. _proto.fill = function fill(bool) {
  20735. if (bool === undefined) {
  20736. return !!this.fill_;
  20737. }
  20738. this.fill_ = !!bool;
  20739. if (bool) {
  20740. this.addClass('vjs-fill');
  20741. this.fluid(false);
  20742. } else {
  20743. this.removeClass('vjs-fill');
  20744. }
  20745. }
  20746. /**
  20747. * Get/Set the aspect ratio
  20748. *
  20749. * @param {string} [ratio]
  20750. * Aspect ratio for player
  20751. *
  20752. * @return {string|undefined}
  20753. * returns the current aspect ratio when getting
  20754. */
  20755. /**
  20756. * A getter/setter for the `Player`'s aspect ratio.
  20757. *
  20758. * @param {string} [ratio]
  20759. * The value to set the `Player's aspect ratio to.
  20760. *
  20761. * @return {string|undefined}
  20762. * - The current aspect ratio of the `Player` when getting.
  20763. * - undefined when setting
  20764. */
  20765. ;
  20766. _proto.aspectRatio = function aspectRatio(ratio) {
  20767. if (ratio === undefined) {
  20768. return this.aspectRatio_;
  20769. } // Check for width:height format
  20770. if (!/^\d+\:\d+$/.test(ratio)) {
  20771. throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
  20772. }
  20773. this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
  20774. // because in fixed mode you could calculate width and height yourself.
  20775. this.fluid(true);
  20776. this.updateStyleEl_();
  20777. }
  20778. /**
  20779. * Update styles of the `Player` element (height, width and aspect ratio).
  20780. *
  20781. * @private
  20782. * @listens Tech#loadedmetadata
  20783. */
  20784. ;
  20785. _proto.updateStyleEl_ = function updateStyleEl_() {
  20786. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
  20787. var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
  20788. var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
  20789. var techEl = this.tech_ && this.tech_.el();
  20790. if (techEl) {
  20791. if (_width >= 0) {
  20792. techEl.width = _width;
  20793. }
  20794. if (_height >= 0) {
  20795. techEl.height = _height;
  20796. }
  20797. }
  20798. return;
  20799. }
  20800. var width;
  20801. var height;
  20802. var aspectRatio;
  20803. var idClass; // The aspect ratio is either used directly or to calculate width and height.
  20804. if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
  20805. // Use any aspectRatio that's been specifically set
  20806. aspectRatio = this.aspectRatio_;
  20807. } else if (this.videoWidth() > 0) {
  20808. // Otherwise try to get the aspect ratio from the video metadata
  20809. aspectRatio = this.videoWidth() + ':' + this.videoHeight();
  20810. } else {
  20811. // Or use a default. The video element's is 2:1, but 16:9 is more common.
  20812. aspectRatio = '16:9';
  20813. } // Get the ratio as a decimal we can use to calculate dimensions
  20814. var ratioParts = aspectRatio.split(':');
  20815. var ratioMultiplier = ratioParts[1] / ratioParts[0];
  20816. if (this.width_ !== undefined) {
  20817. // Use any width that's been specifically set
  20818. width = this.width_;
  20819. } else if (this.height_ !== undefined) {
  20820. // Or calulate the width from the aspect ratio if a height has been set
  20821. width = this.height_ / ratioMultiplier;
  20822. } else {
  20823. // Or use the video's metadata, or use the video el's default of 300
  20824. width = this.videoWidth() || 300;
  20825. }
  20826. if (this.height_ !== undefined) {
  20827. // Use any height that's been specifically set
  20828. height = this.height_;
  20829. } else {
  20830. // Otherwise calculate the height from the ratio and the width
  20831. height = width * ratioMultiplier;
  20832. } // Ensure the CSS class is valid by starting with an alpha character
  20833. if (/^[^a-zA-Z]/.test(this.id())) {
  20834. idClass = 'dimensions-' + this.id();
  20835. } else {
  20836. idClass = this.id() + '-dimensions';
  20837. } // Ensure the right class is still on the player for the style element
  20838. this.addClass(idClass);
  20839. 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 ");
  20840. }
  20841. /**
  20842. * Load/Create an instance of playback {@link Tech} including element
  20843. * and API methods. Then append the `Tech` element in `Player` as a child.
  20844. *
  20845. * @param {string} techName
  20846. * name of the playback technology
  20847. *
  20848. * @param {string} source
  20849. * video source
  20850. *
  20851. * @private
  20852. */
  20853. ;
  20854. _proto.loadTech_ = function loadTech_(techName, source) {
  20855. var _this3 = this;
  20856. // Pause and remove current playback technology
  20857. if (this.tech_) {
  20858. this.unloadTech_();
  20859. }
  20860. var titleTechName = toTitleCase(techName);
  20861. var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
  20862. if (titleTechName !== 'Html5' && this.tag) {
  20863. Tech.getTech('Html5').disposeMediaElement(this.tag);
  20864. this.tag.player = null;
  20865. this.tag = null;
  20866. }
  20867. this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
  20868. this.isReady_ = false; // if autoplay is a string we pass false to the tech
  20869. // because the player is going to handle autoplay on `loadstart`
  20870. var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use.
  20871. var techOptions = {
  20872. source: source,
  20873. autoplay: autoplay,
  20874. 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
  20875. 'playerId': this.id(),
  20876. 'techId': this.id() + "_" + camelTechName + "_api",
  20877. 'playsinline': this.options_.playsinline,
  20878. 'preload': this.options_.preload,
  20879. 'loop': this.options_.loop,
  20880. 'muted': this.options_.muted,
  20881. 'poster': this.poster(),
  20882. 'language': this.language(),
  20883. 'playerElIngest': this.playerElIngest_ || false,
  20884. 'vtt.js': this.options_['vtt.js'],
  20885. 'canOverridePoster': !!this.options_.techCanOverridePoster,
  20886. 'enableSourceset': this.options_.enableSourceset,
  20887. 'Promise': this.options_.Promise
  20888. };
  20889. ALL.names.forEach(function (name) {
  20890. var props = ALL[name];
  20891. techOptions[props.getterName] = _this3[props.privateName];
  20892. });
  20893. assign(techOptions, this.options_[titleTechName]);
  20894. assign(techOptions, this.options_[camelTechName]);
  20895. assign(techOptions, this.options_[techName.toLowerCase()]);
  20896. if (this.tag) {
  20897. techOptions.tag = this.tag;
  20898. }
  20899. if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
  20900. techOptions.startTime = this.cache_.currentTime;
  20901. } // Initialize tech instance
  20902. var TechClass = Tech.getTech(techName);
  20903. if (!TechClass) {
  20904. throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
  20905. }
  20906. this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
  20907. this.tech_.ready(bind(this, this.handleTechReady_), true);
  20908. textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
  20909. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  20910. _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]);
  20911. });
  20912. Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
  20913. _this3.on(_this3.tech_, event, function (eventObj) {
  20914. if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) {
  20915. _this3.queuedCallbacks_.push({
  20916. callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3),
  20917. event: eventObj
  20918. });
  20919. return;
  20920. }
  20921. _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
  20922. });
  20923. });
  20924. this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
  20925. this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
  20926. this.on(this.tech_, 'waiting', this.handleTechWaiting_);
  20927. this.on(this.tech_, 'ended', this.handleTechEnded_);
  20928. this.on(this.tech_, 'seeking', this.handleTechSeeking_);
  20929. this.on(this.tech_, 'play', this.handleTechPlay_);
  20930. this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
  20931. this.on(this.tech_, 'pause', this.handleTechPause_);
  20932. this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
  20933. this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
  20934. this.on(this.tech_, 'enterpictureinpicture', this.handleTechEnterPictureInPicture_);
  20935. this.on(this.tech_, 'leavepictureinpicture', this.handleTechLeavePictureInPicture_);
  20936. this.on(this.tech_, 'error', this.handleTechError_);
  20937. this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
  20938. this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
  20939. this.on(this.tech_, 'textdata', this.handleTechTextData_);
  20940. this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
  20941. this.usingNativeControls(this.techGet_('controls'));
  20942. if (this.controls() && !this.usingNativeControls()) {
  20943. this.addTechControlsListeners_();
  20944. } // Add the tech element in the DOM if it was not already there
  20945. // Make sure to not insert the original video element if using Html5
  20946. if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
  20947. prependTo(this.tech_.el(), this.el());
  20948. } // Get rid of the original video tag reference after the first tech is loaded
  20949. if (this.tag) {
  20950. this.tag.player = null;
  20951. this.tag = null;
  20952. }
  20953. }
  20954. /**
  20955. * Unload and dispose of the current playback {@link Tech}.
  20956. *
  20957. * @private
  20958. */
  20959. ;
  20960. _proto.unloadTech_ = function unloadTech_() {
  20961. var _this4 = this;
  20962. // Save the current text tracks so that we can reuse the same text tracks with the next tech
  20963. ALL.names.forEach(function (name) {
  20964. var props = ALL[name];
  20965. _this4[props.privateName] = _this4[props.getterName]();
  20966. });
  20967. this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
  20968. this.isReady_ = false;
  20969. this.tech_.dispose();
  20970. this.tech_ = false;
  20971. if (this.isPosterFromTech_) {
  20972. this.poster_ = '';
  20973. this.trigger('posterchange');
  20974. }
  20975. this.isPosterFromTech_ = false;
  20976. }
  20977. /**
  20978. * Return a reference to the current {@link Tech}.
  20979. * It will print a warning by default about the danger of using the tech directly
  20980. * but any argument that is passed in will silence the warning.
  20981. *
  20982. * @param {*} [safety]
  20983. * Anything passed in to silence the warning
  20984. *
  20985. * @return {Tech}
  20986. * The Tech
  20987. */
  20988. ;
  20989. _proto.tech = function tech(safety) {
  20990. if (safety === undefined) {
  20991. log.warn(tsml(_templateObject$2()));
  20992. }
  20993. return this.tech_;
  20994. }
  20995. /**
  20996. * Set up click and touch listeners for the playback element
  20997. *
  20998. * - On desktops: a click on the video itself will toggle playback
  20999. * - On mobile devices: a click on the video toggles controls
  21000. * which is done by toggling the user state between active and
  21001. * inactive
  21002. * - A tap can signal that a user has become active or has become inactive
  21003. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  21004. * quick tap should hide them again (signaling the user is in an inactive
  21005. * viewing state)
  21006. * - In addition to this, we still want the user to be considered inactive after
  21007. * a few seconds of inactivity.
  21008. *
  21009. * > Note: the only part of iOS interaction we can't mimic with this setup
  21010. * is a touch and hold on the video element counting as activity in order to
  21011. * keep the controls showing, but that shouldn't be an issue. A touch and hold
  21012. * on any controls will still keep the user active
  21013. *
  21014. * @private
  21015. */
  21016. ;
  21017. _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
  21018. // Make sure to remove all the previous listeners in case we are called multiple times.
  21019. this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
  21020. // trigger mousedown/up.
  21021. // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
  21022. // Any touch events are set to block the mousedown event from happening
  21023. this.on(this.tech_, 'mouseup', this.handleTechClick_);
  21024. this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
  21025. // so we'll check if the controls were already showing before reporting user
  21026. // activity
  21027. this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
  21028. this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
  21029. this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
  21030. // listener cancels out any reportedUserActivity when setting userActive(false)
  21031. this.on(this.tech_, 'tap', this.handleTechTap_);
  21032. }
  21033. /**
  21034. * Remove the listeners used for click and tap controls. This is needed for
  21035. * toggling to controls disabled, where a tap/touch should do nothing.
  21036. *
  21037. * @private
  21038. */
  21039. ;
  21040. _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
  21041. // We don't want to just use `this.off()` because there might be other needed
  21042. // listeners added by techs that extend this.
  21043. this.off(this.tech_, 'tap', this.handleTechTap_);
  21044. this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
  21045. this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
  21046. this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
  21047. this.off(this.tech_, 'mouseup', this.handleTechClick_);
  21048. this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
  21049. }
  21050. /**
  21051. * Player waits for the tech to be ready
  21052. *
  21053. * @private
  21054. */
  21055. ;
  21056. _proto.handleTechReady_ = function handleTechReady_() {
  21057. this.triggerReady(); // Keep the same volume as before
  21058. if (this.cache_.volume) {
  21059. this.techCall_('setVolume', this.cache_.volume);
  21060. } // Look if the tech found a higher resolution poster while loading
  21061. this.handleTechPosterChange_(); // Update the duration if available
  21062. this.handleTechDurationChange_();
  21063. }
  21064. /**
  21065. * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
  21066. * function will also trigger {@link Player#firstplay} if it is the first loadstart
  21067. * for a video.
  21068. *
  21069. * @fires Player#loadstart
  21070. * @fires Player#firstplay
  21071. * @listens Tech#loadstart
  21072. * @private
  21073. */
  21074. ;
  21075. _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
  21076. // TODO: Update to use `emptied` event instead. See #1277.
  21077. this.removeClass('vjs-ended');
  21078. this.removeClass('vjs-seeking'); // reset the error state
  21079. this.error(null); // Update the duration
  21080. this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
  21081. // The firstplay event relies on both the play and loadstart events
  21082. // which can happen in any order for a new source
  21083. if (!this.paused()) {
  21084. /**
  21085. * Fired when the user agent begins looking for media data
  21086. *
  21087. * @event Player#loadstart
  21088. * @type {EventTarget~Event}
  21089. */
  21090. this.trigger('loadstart');
  21091. this.trigger('firstplay');
  21092. } else {
  21093. // reset the hasStarted state
  21094. this.hasStarted(false);
  21095. this.trigger('loadstart');
  21096. } // autoplay happens after loadstart for the browser,
  21097. // so we mimic that behavior
  21098. this.manualAutoplay_(this.autoplay());
  21099. }
  21100. /**
  21101. * Handle autoplay string values, rather than the typical boolean
  21102. * values that should be handled by the tech. Note that this is not
  21103. * part of any specification. Valid values and what they do can be
  21104. * found on the autoplay getter at Player#autoplay()
  21105. */
  21106. ;
  21107. _proto.manualAutoplay_ = function manualAutoplay_(type) {
  21108. var _this5 = this;
  21109. if (!this.tech_ || typeof type !== 'string') {
  21110. return;
  21111. }
  21112. var muted = function muted() {
  21113. var previouslyMuted = _this5.muted();
  21114. _this5.muted(true);
  21115. var restoreMuted = function restoreMuted() {
  21116. _this5.muted(previouslyMuted);
  21117. }; // restore muted on play terminatation
  21118. _this5.playTerminatedQueue_.push(restoreMuted);
  21119. var mutedPromise = _this5.play();
  21120. if (!isPromise(mutedPromise)) {
  21121. return;
  21122. }
  21123. return mutedPromise["catch"](restoreMuted);
  21124. };
  21125. var promise; // if muted defaults to true
  21126. // the only thing we can do is call play
  21127. if (type === 'any' && this.muted() !== true) {
  21128. promise = this.play();
  21129. if (isPromise(promise)) {
  21130. promise = promise["catch"](muted);
  21131. }
  21132. } else if (type === 'muted' && this.muted() !== true) {
  21133. promise = muted();
  21134. } else {
  21135. promise = this.play();
  21136. }
  21137. if (!isPromise(promise)) {
  21138. return;
  21139. }
  21140. return promise.then(function () {
  21141. _this5.trigger({
  21142. type: 'autoplay-success',
  21143. autoplay: type
  21144. });
  21145. })["catch"](function (e) {
  21146. _this5.trigger({
  21147. type: 'autoplay-failure',
  21148. autoplay: type
  21149. });
  21150. });
  21151. }
  21152. /**
  21153. * Update the internal source caches so that we return the correct source from
  21154. * `src()`, `currentSource()`, and `currentSources()`.
  21155. *
  21156. * > Note: `currentSources` will not be updated if the source that is passed in exists
  21157. * in the current `currentSources` cache.
  21158. *
  21159. *
  21160. * @param {Tech~SourceObject} srcObj
  21161. * A string or object source to update our caches to.
  21162. */
  21163. ;
  21164. _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
  21165. if (srcObj === void 0) {
  21166. srcObj = '';
  21167. }
  21168. var src = srcObj;
  21169. var type = '';
  21170. if (typeof src !== 'string') {
  21171. src = srcObj.src;
  21172. type = srcObj.type;
  21173. } // make sure all the caches are set to default values
  21174. // to prevent null checking
  21175. this.cache_.source = this.cache_.source || {};
  21176. this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
  21177. if (src && !type) {
  21178. type = findMimetype(this, src);
  21179. } // update `currentSource` cache always
  21180. this.cache_.source = mergeOptions({}, srcObj, {
  21181. src: src,
  21182. type: type
  21183. });
  21184. var matchingSources = this.cache_.sources.filter(function (s) {
  21185. return s.src && s.src === src;
  21186. });
  21187. var sourceElSources = [];
  21188. var sourceEls = this.$$('source');
  21189. var matchingSourceEls = [];
  21190. for (var i = 0; i < sourceEls.length; i++) {
  21191. var sourceObj = getAttributes(sourceEls[i]);
  21192. sourceElSources.push(sourceObj);
  21193. if (sourceObj.src && sourceObj.src === src) {
  21194. matchingSourceEls.push(sourceObj.src);
  21195. }
  21196. } // if we have matching source els but not matching sources
  21197. // the current source cache is not up to date
  21198. if (matchingSourceEls.length && !matchingSources.length) {
  21199. this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
  21200. // sources cache to the `currentSource` cache
  21201. } else if (!matchingSources.length) {
  21202. this.cache_.sources = [this.cache_.source];
  21203. } // update the tech `src` cache
  21204. this.cache_.src = src;
  21205. }
  21206. /**
  21207. * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
  21208. * causing the media element to reload.
  21209. *
  21210. * It will fire for the initial source and each subsequent source.
  21211. * This event is a custom event from Video.js and is triggered by the {@link Tech}.
  21212. *
  21213. * The event object for this event contains a `src` property that will contain the source
  21214. * that was available when the event was triggered. This is generally only necessary if Video.js
  21215. * is switching techs while the source was being changed.
  21216. *
  21217. * It is also fired when `load` is called on the player (or media element)
  21218. * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
  21219. * says that the resource selection algorithm needs to be aborted and restarted.
  21220. * In this case, it is very likely that the `src` property will be set to the
  21221. * empty string `""` to indicate we do not know what the source will be but
  21222. * that it is changing.
  21223. *
  21224. * *This event is currently still experimental and may change in minor releases.*
  21225. * __To use this, pass `enableSourceset` option to the player.__
  21226. *
  21227. * @event Player#sourceset
  21228. * @type {EventTarget~Event}
  21229. * @prop {string} src
  21230. * The source url available when the `sourceset` was triggered.
  21231. * It will be an empty string if we cannot know what the source is
  21232. * but know that the source will change.
  21233. */
  21234. /**
  21235. * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
  21236. *
  21237. * @fires Player#sourceset
  21238. * @listens Tech#sourceset
  21239. * @private
  21240. */
  21241. ;
  21242. _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
  21243. var _this6 = this;
  21244. // only update the source cache when the source
  21245. // was not updated using the player api
  21246. if (!this.changingSrc_) {
  21247. var updateSourceCaches = function updateSourceCaches(src) {
  21248. return _this6.updateSourceCaches_(src);
  21249. };
  21250. var playerSrc = this.currentSource().src;
  21251. var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
  21252. if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
  21253. // if both the tech source and the player source were updated we assume
  21254. // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
  21255. if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
  21256. updateSourceCaches = function updateSourceCaches() {};
  21257. }
  21258. } // update the source to the intial source right away
  21259. // in some cases this will be empty string
  21260. updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
  21261. // wait for a `loadstart` to update the cache to `currentSrc`.
  21262. // If a sourceset happens before a `loadstart`, we reset the state
  21263. // as this function will be called again.
  21264. if (!event.src) {
  21265. var updateCache = function updateCache(e) {
  21266. if (e.type !== 'sourceset') {
  21267. var techSrc = _this6.techGet('currentSrc');
  21268. _this6.lastSource_.tech = techSrc;
  21269. _this6.updateSourceCaches_(techSrc);
  21270. }
  21271. _this6.tech_.off(['sourceset', 'loadstart'], updateCache);
  21272. };
  21273. this.tech_.one(['sourceset', 'loadstart'], updateCache);
  21274. }
  21275. }
  21276. this.lastSource_ = {
  21277. player: this.currentSource().src,
  21278. tech: event.src
  21279. };
  21280. this.trigger({
  21281. src: event.src,
  21282. type: 'sourceset'
  21283. });
  21284. }
  21285. /**
  21286. * Add/remove the vjs-has-started class
  21287. *
  21288. * @fires Player#firstplay
  21289. *
  21290. * @param {boolean} request
  21291. * - true: adds the class
  21292. * - false: remove the class
  21293. *
  21294. * @return {boolean}
  21295. * the boolean value of hasStarted_
  21296. */
  21297. ;
  21298. _proto.hasStarted = function hasStarted(request) {
  21299. if (request === undefined) {
  21300. // act as getter, if we have no request to change
  21301. return this.hasStarted_;
  21302. }
  21303. if (request === this.hasStarted_) {
  21304. return;
  21305. }
  21306. this.hasStarted_ = request;
  21307. if (this.hasStarted_) {
  21308. this.addClass('vjs-has-started');
  21309. this.trigger('firstplay');
  21310. } else {
  21311. this.removeClass('vjs-has-started');
  21312. }
  21313. }
  21314. /**
  21315. * Fired whenever the media begins or resumes playback
  21316. *
  21317. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
  21318. * @fires Player#play
  21319. * @listens Tech#play
  21320. * @private
  21321. */
  21322. ;
  21323. _proto.handleTechPlay_ = function handleTechPlay_() {
  21324. this.removeClass('vjs-ended');
  21325. this.removeClass('vjs-paused');
  21326. this.addClass('vjs-playing'); // hide the poster when the user hits play
  21327. this.hasStarted(true);
  21328. /**
  21329. * Triggered whenever an {@link Tech#play} event happens. Indicates that
  21330. * playback has started or resumed.
  21331. *
  21332. * @event Player#play
  21333. * @type {EventTarget~Event}
  21334. */
  21335. this.trigger('play');
  21336. }
  21337. /**
  21338. * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
  21339. *
  21340. * If there were any events queued while the playback rate was zero, fire
  21341. * those events now.
  21342. *
  21343. * @private
  21344. * @method Player#handleTechRateChange_
  21345. * @fires Player#ratechange
  21346. * @listens Tech#ratechange
  21347. */
  21348. ;
  21349. _proto.handleTechRateChange_ = function handleTechRateChange_() {
  21350. if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
  21351. this.queuedCallbacks_.forEach(function (queued) {
  21352. return queued.callback(queued.event);
  21353. });
  21354. this.queuedCallbacks_ = [];
  21355. }
  21356. this.cache_.lastPlaybackRate = this.tech_.playbackRate();
  21357. /**
  21358. * Fires when the playing speed of the audio/video is changed
  21359. *
  21360. * @event Player#ratechange
  21361. * @type {event}
  21362. */
  21363. this.trigger('ratechange');
  21364. }
  21365. /**
  21366. * Retrigger the `waiting` event that was triggered by the {@link Tech}.
  21367. *
  21368. * @fires Player#waiting
  21369. * @listens Tech#waiting
  21370. * @private
  21371. */
  21372. ;
  21373. _proto.handleTechWaiting_ = function handleTechWaiting_() {
  21374. var _this7 = this;
  21375. this.addClass('vjs-waiting');
  21376. /**
  21377. * A readyState change on the DOM element has caused playback to stop.
  21378. *
  21379. * @event Player#waiting
  21380. * @type {EventTarget~Event}
  21381. */
  21382. this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
  21383. // premature removal of the waiting class, wait for the time to change.
  21384. var timeWhenWaiting = this.currentTime();
  21385. var timeUpdateListener = function timeUpdateListener() {
  21386. if (timeWhenWaiting !== _this7.currentTime()) {
  21387. _this7.removeClass('vjs-waiting');
  21388. _this7.off('timeupdate', timeUpdateListener);
  21389. }
  21390. };
  21391. this.on('timeupdate', timeUpdateListener);
  21392. }
  21393. /**
  21394. * Retrigger the `canplay` event that was triggered by the {@link Tech}.
  21395. * > Note: This is not consistent between browsers. See #1351
  21396. *
  21397. * @fires Player#canplay
  21398. * @listens Tech#canplay
  21399. * @private
  21400. */
  21401. ;
  21402. _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
  21403. this.removeClass('vjs-waiting');
  21404. /**
  21405. * The media has a readyState of HAVE_FUTURE_DATA or greater.
  21406. *
  21407. * @event Player#canplay
  21408. * @type {EventTarget~Event}
  21409. */
  21410. this.trigger('canplay');
  21411. }
  21412. /**
  21413. * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
  21414. *
  21415. * @fires Player#canplaythrough
  21416. * @listens Tech#canplaythrough
  21417. * @private
  21418. */
  21419. ;
  21420. _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
  21421. this.removeClass('vjs-waiting');
  21422. /**
  21423. * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
  21424. * entire media file can be played without buffering.
  21425. *
  21426. * @event Player#canplaythrough
  21427. * @type {EventTarget~Event}
  21428. */
  21429. this.trigger('canplaythrough');
  21430. }
  21431. /**
  21432. * Retrigger the `playing` event that was triggered by the {@link Tech}.
  21433. *
  21434. * @fires Player#playing
  21435. * @listens Tech#playing
  21436. * @private
  21437. */
  21438. ;
  21439. _proto.handleTechPlaying_ = function handleTechPlaying_() {
  21440. this.removeClass('vjs-waiting');
  21441. /**
  21442. * The media is no longer blocked from playback, and has started playing.
  21443. *
  21444. * @event Player#playing
  21445. * @type {EventTarget~Event}
  21446. */
  21447. this.trigger('playing');
  21448. }
  21449. /**
  21450. * Retrigger the `seeking` event that was triggered by the {@link Tech}.
  21451. *
  21452. * @fires Player#seeking
  21453. * @listens Tech#seeking
  21454. * @private
  21455. */
  21456. ;
  21457. _proto.handleTechSeeking_ = function handleTechSeeking_() {
  21458. this.addClass('vjs-seeking');
  21459. /**
  21460. * Fired whenever the player is jumping to a new time
  21461. *
  21462. * @event Player#seeking
  21463. * @type {EventTarget~Event}
  21464. */
  21465. this.trigger('seeking');
  21466. }
  21467. /**
  21468. * Retrigger the `seeked` event that was triggered by the {@link Tech}.
  21469. *
  21470. * @fires Player#seeked
  21471. * @listens Tech#seeked
  21472. * @private
  21473. */
  21474. ;
  21475. _proto.handleTechSeeked_ = function handleTechSeeked_() {
  21476. this.removeClass('vjs-seeking');
  21477. this.removeClass('vjs-ended');
  21478. /**
  21479. * Fired when the player has finished jumping to a new time
  21480. *
  21481. * @event Player#seeked
  21482. * @type {EventTarget~Event}
  21483. */
  21484. this.trigger('seeked');
  21485. }
  21486. /**
  21487. * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
  21488. *
  21489. * @fires Player#firstplay
  21490. * @listens Tech#firstplay
  21491. * @deprecated As of 6.0 firstplay event is deprecated.
  21492. * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
  21493. * @private
  21494. */
  21495. ;
  21496. _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
  21497. // If the first starttime attribute is specified
  21498. // then we will start at the given offset in seconds
  21499. if (this.options_.starttime) {
  21500. log.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
  21501. this.currentTime(this.options_.starttime);
  21502. }
  21503. this.addClass('vjs-has-started');
  21504. /**
  21505. * Fired the first time a video is played. Not part of the HLS spec, and this is
  21506. * probably not the best implementation yet, so use sparingly. If you don't have a
  21507. * reason to prevent playback, use `myPlayer.one('play');` instead.
  21508. *
  21509. * @event Player#firstplay
  21510. * @deprecated As of 6.0 firstplay event is deprecated.
  21511. * @type {EventTarget~Event}
  21512. */
  21513. this.trigger('firstplay');
  21514. }
  21515. /**
  21516. * Retrigger the `pause` event that was triggered by the {@link Tech}.
  21517. *
  21518. * @fires Player#pause
  21519. * @listens Tech#pause
  21520. * @private
  21521. */
  21522. ;
  21523. _proto.handleTechPause_ = function handleTechPause_() {
  21524. this.removeClass('vjs-playing');
  21525. this.addClass('vjs-paused');
  21526. /**
  21527. * Fired whenever the media has been paused
  21528. *
  21529. * @event Player#pause
  21530. * @type {EventTarget~Event}
  21531. */
  21532. this.trigger('pause');
  21533. }
  21534. /**
  21535. * Retrigger the `ended` event that was triggered by the {@link Tech}.
  21536. *
  21537. * @fires Player#ended
  21538. * @listens Tech#ended
  21539. * @private
  21540. */
  21541. ;
  21542. _proto.handleTechEnded_ = function handleTechEnded_() {
  21543. this.addClass('vjs-ended');
  21544. if (this.options_.loop) {
  21545. this.currentTime(0);
  21546. this.play();
  21547. } else if (!this.paused()) {
  21548. this.pause();
  21549. }
  21550. /**
  21551. * Fired when the end of the media resource is reached (currentTime == duration)
  21552. *
  21553. * @event Player#ended
  21554. * @type {EventTarget~Event}
  21555. */
  21556. this.trigger('ended');
  21557. }
  21558. /**
  21559. * Fired when the duration of the media resource is first known or changed
  21560. *
  21561. * @listens Tech#durationchange
  21562. * @private
  21563. */
  21564. ;
  21565. _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
  21566. this.duration(this.techGet_('duration'));
  21567. }
  21568. /**
  21569. * Handle a click on the media element to play/pause
  21570. *
  21571. * @param {EventTarget~Event} event
  21572. * the event that caused this function to trigger
  21573. *
  21574. * @listens Tech#mouseup
  21575. * @private
  21576. */
  21577. ;
  21578. _proto.handleTechClick_ = function handleTechClick_(event) {
  21579. if (!isSingleLeftClick(event)) {
  21580. return;
  21581. } // When controls are disabled a click should not toggle playback because
  21582. // the click is considered a control
  21583. if (!this.controls_) {
  21584. return;
  21585. }
  21586. if (this.paused()) {
  21587. silencePromise(this.play());
  21588. } else {
  21589. this.pause();
  21590. }
  21591. }
  21592. /**
  21593. * Handle a double-click on the media element to enter/exit fullscreen
  21594. *
  21595. * @param {EventTarget~Event} event
  21596. * the event that caused this function to trigger
  21597. *
  21598. * @listens Tech#dblclick
  21599. * @private
  21600. */
  21601. ;
  21602. _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
  21603. if (!this.controls_) {
  21604. return;
  21605. } // we do not want to toggle fullscreen state
  21606. // when double-clicking inside a control bar or a modal
  21607. var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
  21608. return el.contains(event.target);
  21609. });
  21610. if (!inAllowedEls) {
  21611. /*
  21612. * options.userActions.doubleClick
  21613. *
  21614. * If `undefined` or `true`, double-click toggles fullscreen if controls are present
  21615. * Set to `false` to disable double-click handling
  21616. * Set to a function to substitute an external double-click handler
  21617. */
  21618. if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
  21619. if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
  21620. this.options_.userActions.doubleClick.call(this, event);
  21621. } else if (this.isFullscreen()) {
  21622. this.exitFullscreen();
  21623. } else {
  21624. this.requestFullscreen();
  21625. }
  21626. }
  21627. }
  21628. }
  21629. /**
  21630. * Handle a tap on the media element. It will toggle the user
  21631. * activity state, which hides and shows the controls.
  21632. *
  21633. * @listens Tech#tap
  21634. * @private
  21635. */
  21636. ;
  21637. _proto.handleTechTap_ = function handleTechTap_() {
  21638. this.userActive(!this.userActive());
  21639. }
  21640. /**
  21641. * Handle touch to start
  21642. *
  21643. * @listens Tech#touchstart
  21644. * @private
  21645. */
  21646. ;
  21647. _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
  21648. this.userWasActive = this.userActive();
  21649. }
  21650. /**
  21651. * Handle touch to move
  21652. *
  21653. * @listens Tech#touchmove
  21654. * @private
  21655. */
  21656. ;
  21657. _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
  21658. if (this.userWasActive) {
  21659. this.reportUserActivity();
  21660. }
  21661. }
  21662. /**
  21663. * Handle touch to end
  21664. *
  21665. * @param {EventTarget~Event} event
  21666. * the touchend event that triggered
  21667. * this function
  21668. *
  21669. * @listens Tech#touchend
  21670. * @private
  21671. */
  21672. ;
  21673. _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
  21674. // Stop the mouse events from also happening
  21675. event.preventDefault();
  21676. }
  21677. /**
  21678. * native click events on the SWF aren't triggered on IE11, Win8.1RT
  21679. * use stageclick events triggered from inside the SWF instead
  21680. *
  21681. * @private
  21682. * @listens stageclick
  21683. */
  21684. ;
  21685. _proto.handleStageClick_ = function handleStageClick_() {
  21686. this.reportUserActivity();
  21687. }
  21688. /**
  21689. * @private
  21690. */
  21691. ;
  21692. _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
  21693. if (this.isFullscreen()) {
  21694. this.addClass('vjs-fullscreen');
  21695. } else {
  21696. this.removeClass('vjs-fullscreen');
  21697. }
  21698. }
  21699. /**
  21700. * when the document fschange event triggers it calls this
  21701. */
  21702. ;
  21703. _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
  21704. var el = this.el();
  21705. var isFs = document[this.fsApi_.fullscreenElement] === el;
  21706. if (!isFs && el.matches) {
  21707. isFs = el.matches(':' + this.fsApi_.fullscreen);
  21708. } else if (!isFs && el.msMatchesSelector) {
  21709. isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
  21710. }
  21711. this.isFullscreen(isFs); // If cancelling fullscreen, remove event listener.
  21712. if (this.isFullscreen() === false) {
  21713. off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
  21714. }
  21715. if (this.fsApi_.prefixed) {
  21716. /**
  21717. * @event Player#fullscreenchange
  21718. * @type {EventTarget~Event}
  21719. */
  21720. this.trigger('fullscreenchange');
  21721. }
  21722. }
  21723. /**
  21724. * Handle Tech Fullscreen Change
  21725. *
  21726. * @param {EventTarget~Event} event
  21727. * the fullscreenchange event that triggered this function
  21728. *
  21729. * @param {Object} data
  21730. * the data that was sent with the event
  21731. *
  21732. * @private
  21733. * @listens Tech#fullscreenchange
  21734. * @fires Player#fullscreenchange
  21735. */
  21736. ;
  21737. _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
  21738. if (data) {
  21739. this.isFullscreen(data.isFullscreen);
  21740. }
  21741. /**
  21742. * Fired when going in and out of fullscreen.
  21743. *
  21744. * @event Player#fullscreenchange
  21745. * @type {EventTarget~Event}
  21746. */
  21747. this.trigger('fullscreenchange');
  21748. }
  21749. /**
  21750. * @private
  21751. */
  21752. ;
  21753. _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() {
  21754. if (this.isInPictureInPicture()) {
  21755. this.addClass('vjs-picture-in-picture');
  21756. } else {
  21757. this.removeClass('vjs-picture-in-picture');
  21758. }
  21759. }
  21760. /**
  21761. * Handle Tech Enter Picture-in-Picture.
  21762. *
  21763. * @param {EventTarget~Event} event
  21764. * the enterpictureinpicture event that triggered this function
  21765. *
  21766. * @private
  21767. * @listens Tech#enterpictureinpicture
  21768. */
  21769. ;
  21770. _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) {
  21771. this.isInPictureInPicture(true);
  21772. }
  21773. /**
  21774. * Handle Tech Leave Picture-in-Picture.
  21775. *
  21776. * @param {EventTarget~Event} event
  21777. * the leavepictureinpicture event that triggered this function
  21778. *
  21779. * @private
  21780. * @listens Tech#leavepictureinpicture
  21781. */
  21782. ;
  21783. _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) {
  21784. this.isInPictureInPicture(false);
  21785. }
  21786. /**
  21787. * Fires when an error occurred during the loading of an audio/video.
  21788. *
  21789. * @private
  21790. * @listens Tech#error
  21791. */
  21792. ;
  21793. _proto.handleTechError_ = function handleTechError_() {
  21794. var error = this.tech_.error();
  21795. this.error(error);
  21796. }
  21797. /**
  21798. * Retrigger the `textdata` event that was triggered by the {@link Tech}.
  21799. *
  21800. * @fires Player#textdata
  21801. * @listens Tech#textdata
  21802. * @private
  21803. */
  21804. ;
  21805. _proto.handleTechTextData_ = function handleTechTextData_() {
  21806. var data = null;
  21807. if (arguments.length > 1) {
  21808. data = arguments[1];
  21809. }
  21810. /**
  21811. * Fires when we get a textdata event from tech
  21812. *
  21813. * @event Player#textdata
  21814. * @type {EventTarget~Event}
  21815. */
  21816. this.trigger('textdata', data);
  21817. }
  21818. /**
  21819. * Get object for cached values.
  21820. *
  21821. * @return {Object}
  21822. * get the current object cache
  21823. */
  21824. ;
  21825. _proto.getCache = function getCache() {
  21826. return this.cache_;
  21827. }
  21828. /**
  21829. * Resets the internal cache object.
  21830. *
  21831. * Using this function outside the player constructor or reset method may
  21832. * have unintended side-effects.
  21833. *
  21834. * @private
  21835. */
  21836. ;
  21837. _proto.resetCache_ = function resetCache_() {
  21838. this.cache_ = {
  21839. // Right now, the currentTime is not _really_ cached because it is always
  21840. // retrieved from the tech (see: currentTime). However, for completeness,
  21841. // we set it to zero here to ensure that if we do start actually caching
  21842. // it, we reset it along with everything else.
  21843. currentTime: 0,
  21844. inactivityTimeout: this.options_.inactivityTimeout,
  21845. duration: NaN,
  21846. lastVolume: 1,
  21847. lastPlaybackRate: this.defaultPlaybackRate(),
  21848. media: null,
  21849. src: '',
  21850. source: {},
  21851. sources: [],
  21852. volume: 1
  21853. };
  21854. }
  21855. /**
  21856. * Pass values to the playback tech
  21857. *
  21858. * @param {string} [method]
  21859. * the method to call
  21860. *
  21861. * @param {Object} arg
  21862. * the argument to pass
  21863. *
  21864. * @private
  21865. */
  21866. ;
  21867. _proto.techCall_ = function techCall_(method, arg) {
  21868. // If it's not ready yet, call method when it is
  21869. this.ready(function () {
  21870. if (method in allowedSetters) {
  21871. return set(this.middleware_, this.tech_, method, arg);
  21872. } else if (method in allowedMediators) {
  21873. return mediate(this.middleware_, this.tech_, method, arg);
  21874. }
  21875. try {
  21876. if (this.tech_) {
  21877. this.tech_[method](arg);
  21878. }
  21879. } catch (e) {
  21880. log(e);
  21881. throw e;
  21882. }
  21883. }, true);
  21884. }
  21885. /**
  21886. * Get calls can't wait for the tech, and sometimes don't need to.
  21887. *
  21888. * @param {string} method
  21889. * Tech method
  21890. *
  21891. * @return {Function|undefined}
  21892. * the method or undefined
  21893. *
  21894. * @private
  21895. */
  21896. ;
  21897. _proto.techGet_ = function techGet_(method) {
  21898. if (!this.tech_ || !this.tech_.isReady_) {
  21899. return;
  21900. }
  21901. if (method in allowedGetters) {
  21902. return get(this.middleware_, this.tech_, method);
  21903. } else if (method in allowedMediators) {
  21904. return mediate(this.middleware_, this.tech_, method);
  21905. } // Flash likes to die and reload when you hide or reposition it.
  21906. // In these cases the object methods go away and we get errors.
  21907. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  21908. try {
  21909. return this.tech_[method]();
  21910. } catch (e) {
  21911. // When building additional tech libs, an expected method may not be defined yet
  21912. if (this.tech_[method] === undefined) {
  21913. log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
  21914. throw e;
  21915. } // When a method isn't available on the object it throws a TypeError
  21916. if (e.name === 'TypeError') {
  21917. log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
  21918. this.tech_.isReady_ = false;
  21919. throw e;
  21920. } // If error unknown, just log and throw
  21921. log(e);
  21922. throw e;
  21923. }
  21924. }
  21925. /**
  21926. * Attempt to begin playback at the first opportunity.
  21927. *
  21928. * @return {Promise|undefined}
  21929. * Returns a promise if the browser supports Promises (or one
  21930. * was passed in as an option). This promise will be resolved on
  21931. * the return value of play. If this is undefined it will fulfill the
  21932. * promise chain otherwise the promise chain will be fulfilled when
  21933. * the promise from play is fulfilled.
  21934. */
  21935. ;
  21936. _proto.play = function play() {
  21937. var _this8 = this;
  21938. var PromiseClass = this.options_.Promise || window$1.Promise;
  21939. if (PromiseClass) {
  21940. return new PromiseClass(function (resolve) {
  21941. _this8.play_(resolve);
  21942. });
  21943. }
  21944. return this.play_();
  21945. }
  21946. /**
  21947. * The actual logic for play, takes a callback that will be resolved on the
  21948. * return value of play. This allows us to resolve to the play promise if there
  21949. * is one on modern browsers.
  21950. *
  21951. * @private
  21952. * @param {Function} [callback]
  21953. * The callback that should be called when the techs play is actually called
  21954. */
  21955. ;
  21956. _proto.play_ = function play_(callback) {
  21957. var _this9 = this;
  21958. if (callback === void 0) {
  21959. callback = silencePromise;
  21960. }
  21961. this.playCallbacks_.push(callback);
  21962. var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
  21963. if (this.waitToPlay_) {
  21964. this.off(['ready', 'loadstart'], this.waitToPlay_);
  21965. this.waitToPlay_ = null;
  21966. } // if the player/tech is not ready or the src itself is not ready
  21967. // queue up a call to play on `ready` or `loadstart`
  21968. if (!this.isReady_ || !isSrcReady) {
  21969. this.waitToPlay_ = function (e) {
  21970. _this9.play_();
  21971. };
  21972. this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
  21973. // in that case, we need to prime the video element by calling load so it'll be ready in time
  21974. if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
  21975. this.load();
  21976. }
  21977. return;
  21978. } // If the player/tech is ready and we have a source, we can attempt playback.
  21979. var val = this.techGet_('play'); // play was terminated if the returned value is null
  21980. if (val === null) {
  21981. this.runPlayTerminatedQueue_();
  21982. } else {
  21983. this.runPlayCallbacks_(val);
  21984. }
  21985. }
  21986. /**
  21987. * These functions will be run when if play is terminated. If play
  21988. * runPlayCallbacks_ is run these function will not be run. This allows us
  21989. * to differenciate between a terminated play and an actual call to play.
  21990. */
  21991. ;
  21992. _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
  21993. var queue = this.playTerminatedQueue_.slice(0);
  21994. this.playTerminatedQueue_ = [];
  21995. queue.forEach(function (q) {
  21996. q();
  21997. });
  21998. }
  21999. /**
  22000. * When a callback to play is delayed we have to run these
  22001. * callbacks when play is actually called on the tech. This function
  22002. * runs the callbacks that were delayed and accepts the return value
  22003. * from the tech.
  22004. *
  22005. * @param {undefined|Promise} val
  22006. * The return value from the tech.
  22007. */
  22008. ;
  22009. _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
  22010. var callbacks = this.playCallbacks_.slice(0);
  22011. this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
  22012. this.playTerminatedQueue_ = [];
  22013. callbacks.forEach(function (cb) {
  22014. cb(val);
  22015. });
  22016. }
  22017. /**
  22018. * Pause the video playback
  22019. *
  22020. * @return {Player}
  22021. * A reference to the player object this function was called on
  22022. */
  22023. ;
  22024. _proto.pause = function pause() {
  22025. this.techCall_('pause');
  22026. }
  22027. /**
  22028. * Check if the player is paused or has yet to play
  22029. *
  22030. * @return {boolean}
  22031. * - false: if the media is currently playing
  22032. * - true: if media is not currently playing
  22033. */
  22034. ;
  22035. _proto.paused = function paused() {
  22036. // The initial state of paused should be true (in Safari it's actually false)
  22037. return this.techGet_('paused') === false ? false : true;
  22038. }
  22039. /**
  22040. * Get a TimeRange object representing the current ranges of time that the user
  22041. * has played.
  22042. *
  22043. * @return {TimeRange}
  22044. * A time range object that represents all the increments of time that have
  22045. * been played.
  22046. */
  22047. ;
  22048. _proto.played = function played() {
  22049. return this.techGet_('played') || createTimeRanges(0, 0);
  22050. }
  22051. /**
  22052. * Returns whether or not the user is "scrubbing". Scrubbing is
  22053. * when the user has clicked the progress bar handle and is
  22054. * dragging it along the progress bar.
  22055. *
  22056. * @param {boolean} [isScrubbing]
  22057. * whether the user is or is not scrubbing
  22058. *
  22059. * @return {boolean}
  22060. * The value of scrubbing when getting
  22061. */
  22062. ;
  22063. _proto.scrubbing = function scrubbing(isScrubbing) {
  22064. if (typeof isScrubbing === 'undefined') {
  22065. return this.scrubbing_;
  22066. }
  22067. this.scrubbing_ = !!isScrubbing;
  22068. if (isScrubbing) {
  22069. this.addClass('vjs-scrubbing');
  22070. } else {
  22071. this.removeClass('vjs-scrubbing');
  22072. }
  22073. }
  22074. /**
  22075. * Get or set the current time (in seconds)
  22076. *
  22077. * @param {number|string} [seconds]
  22078. * The time to seek to in seconds
  22079. *
  22080. * @return {number}
  22081. * - the current time in seconds when getting
  22082. */
  22083. ;
  22084. _proto.currentTime = function currentTime(seconds) {
  22085. if (typeof seconds !== 'undefined') {
  22086. if (seconds < 0) {
  22087. seconds = 0;
  22088. }
  22089. this.techCall_('setCurrentTime', seconds);
  22090. return;
  22091. } // cache last currentTime and return. default to 0 seconds
  22092. //
  22093. // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
  22094. // currentTime when scrubbing, but may not provide much performance benefit afterall.
  22095. // Should be tested. Also something has to read the actual current time or the cache will
  22096. // never get updated.
  22097. this.cache_.currentTime = this.techGet_('currentTime') || 0;
  22098. return this.cache_.currentTime;
  22099. }
  22100. /**
  22101. * Normally gets the length in time of the video in seconds;
  22102. * in all but the rarest use cases an argument will NOT be passed to the method
  22103. *
  22104. * > **NOTE**: The video must have started loading before the duration can be
  22105. * known, and in the case of Flash, may not be known until the video starts
  22106. * playing.
  22107. *
  22108. * @fires Player#durationchange
  22109. *
  22110. * @param {number} [seconds]
  22111. * The duration of the video to set in seconds
  22112. *
  22113. * @return {number}
  22114. * - The duration of the video in seconds when getting
  22115. */
  22116. ;
  22117. _proto.duration = function duration(seconds) {
  22118. if (seconds === undefined) {
  22119. // return NaN if the duration is not known
  22120. return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
  22121. }
  22122. seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
  22123. if (seconds < 0) {
  22124. seconds = Infinity;
  22125. }
  22126. if (seconds !== this.cache_.duration) {
  22127. // Cache the last set value for optimized scrubbing (esp. Flash)
  22128. this.cache_.duration = seconds;
  22129. if (seconds === Infinity) {
  22130. this.addClass('vjs-live');
  22131. if (this.options_.liveui && this.player_.liveTracker) {
  22132. this.addClass('vjs-liveui');
  22133. }
  22134. } else {
  22135. this.removeClass('vjs-live');
  22136. this.removeClass('vjs-liveui');
  22137. }
  22138. if (!isNaN(seconds)) {
  22139. // Do not fire durationchange unless the duration value is known.
  22140. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  22141. /**
  22142. * @event Player#durationchange
  22143. * @type {EventTarget~Event}
  22144. */
  22145. this.trigger('durationchange');
  22146. }
  22147. }
  22148. }
  22149. /**
  22150. * Calculates how much time is left in the video. Not part
  22151. * of the native video API.
  22152. *
  22153. * @return {number}
  22154. * The time remaining in seconds
  22155. */
  22156. ;
  22157. _proto.remainingTime = function remainingTime() {
  22158. return this.duration() - this.currentTime();
  22159. }
  22160. /**
  22161. * A remaining time function that is intented to be used when
  22162. * the time is to be displayed directly to the user.
  22163. *
  22164. * @return {number}
  22165. * The rounded time remaining in seconds
  22166. */
  22167. ;
  22168. _proto.remainingTimeDisplay = function remainingTimeDisplay() {
  22169. return Math.floor(this.duration()) - Math.floor(this.currentTime());
  22170. } //
  22171. // Kind of like an array of portions of the video that have been downloaded.
  22172. /**
  22173. * Get a TimeRange object with an array of the times of the video
  22174. * that have been downloaded. If you just want the percent of the
  22175. * video that's been downloaded, use bufferedPercent.
  22176. *
  22177. * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
  22178. *
  22179. * @return {TimeRange}
  22180. * A mock TimeRange object (following HTML spec)
  22181. */
  22182. ;
  22183. _proto.buffered = function buffered() {
  22184. var buffered = this.techGet_('buffered');
  22185. if (!buffered || !buffered.length) {
  22186. buffered = createTimeRanges(0, 0);
  22187. }
  22188. return buffered;
  22189. }
  22190. /**
  22191. * Get the percent (as a decimal) of the video that's been downloaded.
  22192. * This method is not a part of the native HTML video API.
  22193. *
  22194. * @return {number}
  22195. * A decimal between 0 and 1 representing the percent
  22196. * that is buffered 0 being 0% and 1 being 100%
  22197. */
  22198. ;
  22199. _proto.bufferedPercent = function bufferedPercent$1() {
  22200. return bufferedPercent(this.buffered(), this.duration());
  22201. }
  22202. /**
  22203. * Get the ending time of the last buffered time range
  22204. * This is used in the progress bar to encapsulate all time ranges.
  22205. *
  22206. * @return {number}
  22207. * The end of the last buffered time range
  22208. */
  22209. ;
  22210. _proto.bufferedEnd = function bufferedEnd() {
  22211. var buffered = this.buffered();
  22212. var duration = this.duration();
  22213. var end = buffered.end(buffered.length - 1);
  22214. if (end > duration) {
  22215. end = duration;
  22216. }
  22217. return end;
  22218. }
  22219. /**
  22220. * Get or set the current volume of the media
  22221. *
  22222. * @param {number} [percentAsDecimal]
  22223. * The new volume as a decimal percent:
  22224. * - 0 is muted/0%/off
  22225. * - 1.0 is 100%/full
  22226. * - 0.5 is half volume or 50%
  22227. *
  22228. * @return {number}
  22229. * The current volume as a percent when getting
  22230. */
  22231. ;
  22232. _proto.volume = function volume(percentAsDecimal) {
  22233. var vol;
  22234. if (percentAsDecimal !== undefined) {
  22235. // Force value to between 0 and 1
  22236. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
  22237. this.cache_.volume = vol;
  22238. this.techCall_('setVolume', vol);
  22239. if (vol > 0) {
  22240. this.lastVolume_(vol);
  22241. }
  22242. return;
  22243. } // Default to 1 when returning current volume.
  22244. vol = parseFloat(this.techGet_('volume'));
  22245. return isNaN(vol) ? 1 : vol;
  22246. }
  22247. /**
  22248. * Get the current muted state, or turn mute on or off
  22249. *
  22250. * @param {boolean} [muted]
  22251. * - true to mute
  22252. * - false to unmute
  22253. *
  22254. * @return {boolean}
  22255. * - true if mute is on and getting
  22256. * - false if mute is off and getting
  22257. */
  22258. ;
  22259. _proto.muted = function muted(_muted) {
  22260. if (_muted !== undefined) {
  22261. this.techCall_('setMuted', _muted);
  22262. return;
  22263. }
  22264. return this.techGet_('muted') || false;
  22265. }
  22266. /**
  22267. * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
  22268. * indicates the state of muted on initial playback.
  22269. *
  22270. * ```js
  22271. * var myPlayer = videojs('some-player-id');
  22272. *
  22273. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  22274. *
  22275. * // get, should be false
  22276. * console.log(myPlayer.defaultMuted());
  22277. * // set to true
  22278. * myPlayer.defaultMuted(true);
  22279. * // get should be true
  22280. * console.log(myPlayer.defaultMuted());
  22281. * ```
  22282. *
  22283. * @param {boolean} [defaultMuted]
  22284. * - true to mute
  22285. * - false to unmute
  22286. *
  22287. * @return {boolean|Player}
  22288. * - true if defaultMuted is on and getting
  22289. * - false if defaultMuted is off and getting
  22290. * - A reference to the current player when setting
  22291. */
  22292. ;
  22293. _proto.defaultMuted = function defaultMuted(_defaultMuted) {
  22294. if (_defaultMuted !== undefined) {
  22295. return this.techCall_('setDefaultMuted', _defaultMuted);
  22296. }
  22297. return this.techGet_('defaultMuted') || false;
  22298. }
  22299. /**
  22300. * Get the last volume, or set it
  22301. *
  22302. * @param {number} [percentAsDecimal]
  22303. * The new last volume as a decimal percent:
  22304. * - 0 is muted/0%/off
  22305. * - 1.0 is 100%/full
  22306. * - 0.5 is half volume or 50%
  22307. *
  22308. * @return {number}
  22309. * the current value of lastVolume as a percent when getting
  22310. *
  22311. * @private
  22312. */
  22313. ;
  22314. _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
  22315. if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
  22316. this.cache_.lastVolume = percentAsDecimal;
  22317. return;
  22318. }
  22319. return this.cache_.lastVolume;
  22320. }
  22321. /**
  22322. * Check if current tech can support native fullscreen
  22323. * (e.g. with built in controls like iOS, so not our flash swf)
  22324. *
  22325. * @return {boolean}
  22326. * if native fullscreen is supported
  22327. */
  22328. ;
  22329. _proto.supportsFullScreen = function supportsFullScreen() {
  22330. return this.techGet_('supportsFullScreen') || false;
  22331. }
  22332. /**
  22333. * Check if the player is in fullscreen mode or tell the player that it
  22334. * is or is not in fullscreen mode.
  22335. *
  22336. * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
  22337. * property and instead document.fullscreenElement is used. But isFullscreen is
  22338. * still a valuable property for internal player workings.
  22339. *
  22340. * @param {boolean} [isFS]
  22341. * Set the players current fullscreen state
  22342. *
  22343. * @return {boolean}
  22344. * - true if fullscreen is on and getting
  22345. * - false if fullscreen is off and getting
  22346. */
  22347. ;
  22348. _proto.isFullscreen = function isFullscreen(isFS) {
  22349. if (isFS !== undefined) {
  22350. this.isFullscreen_ = !!isFS;
  22351. this.toggleFullscreenClass_();
  22352. return;
  22353. }
  22354. return !!this.isFullscreen_;
  22355. }
  22356. /**
  22357. * Increase the size of the video to full screen
  22358. * In some browsers, full screen is not supported natively, so it enters
  22359. * "full window mode", where the video fills the browser window.
  22360. * In browsers and devices that support native full screen, sometimes the
  22361. * browser's default controls will be shown, and not the Video.js custom skin.
  22362. * This includes most mobile devices (iOS, Android) and older versions of
  22363. * Safari.
  22364. *
  22365. * @param {Object} [fullscreenOptions]
  22366. * Override the player fullscreen options
  22367. *
  22368. * @fires Player#fullscreenchange
  22369. */
  22370. ;
  22371. _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) {
  22372. var fsOptions;
  22373. this.isFullscreen(true);
  22374. if (this.fsApi_.requestFullscreen) {
  22375. // the browser supports going fullscreen at the element level so we can
  22376. // take the controls fullscreen as well as the video
  22377. // Trigger fullscreenchange event after change
  22378. // We have to specifically add this each time, and remove
  22379. // when canceling fullscreen. Otherwise if there's multiple
  22380. // players on a page, they would all be reacting to the same fullscreen
  22381. // events
  22382. on(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_); // only pass FullscreenOptions to requestFullscreen if it isn't prefixed
  22383. if (!this.fsApi_.prefixed) {
  22384. fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
  22385. if (fullscreenOptions !== undefined) {
  22386. fsOptions = fullscreenOptions;
  22387. }
  22388. }
  22389. silencePromise(this.el_[this.fsApi_.requestFullscreen](fsOptions));
  22390. } else if (this.tech_.supportsFullScreen()) {
  22391. // we can't take the video.js controls fullscreen but we can go fullscreen
  22392. // with native controls
  22393. this.techCall_('enterFullScreen');
  22394. } else {
  22395. // fullscreen isn't supported so we'll just stretch the video element to
  22396. // fill the viewport
  22397. this.enterFullWindow();
  22398. /**
  22399. * @event Player#fullscreenchange
  22400. * @type {EventTarget~Event}
  22401. */
  22402. this.trigger('fullscreenchange');
  22403. }
  22404. }
  22405. /**
  22406. * Return the video to its normal size after having been in full screen mode
  22407. *
  22408. * @fires Player#fullscreenchange
  22409. */
  22410. ;
  22411. _proto.exitFullscreen = function exitFullscreen() {
  22412. this.isFullscreen(false); // Check for browser element fullscreen support
  22413. if (this.fsApi_.requestFullscreen) {
  22414. silencePromise(document[this.fsApi_.exitFullscreen]());
  22415. } else if (this.tech_.supportsFullScreen()) {
  22416. this.techCall_('exitFullScreen');
  22417. } else {
  22418. this.exitFullWindow();
  22419. /**
  22420. * @event Player#fullscreenchange
  22421. * @type {EventTarget~Event}
  22422. */
  22423. this.trigger('fullscreenchange');
  22424. }
  22425. }
  22426. /**
  22427. * When fullscreen isn't supported we can stretch the
  22428. * video container to as wide as the browser will let us.
  22429. *
  22430. * @fires Player#enterFullWindow
  22431. */
  22432. ;
  22433. _proto.enterFullWindow = function enterFullWindow() {
  22434. this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
  22435. this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
  22436. on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
  22437. document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
  22438. addClass(document.body, 'vjs-full-window');
  22439. /**
  22440. * @event Player#enterFullWindow
  22441. * @type {EventTarget~Event}
  22442. */
  22443. this.trigger('enterFullWindow');
  22444. }
  22445. /**
  22446. * Check for call to either exit full window or
  22447. * full screen on ESC key
  22448. *
  22449. * @param {string} event
  22450. * Event to check for key press
  22451. */
  22452. ;
  22453. _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
  22454. if (keycode.isEventKey(event, 'Esc')) {
  22455. if (this.isFullscreen() === true) {
  22456. this.exitFullscreen();
  22457. } else {
  22458. this.exitFullWindow();
  22459. }
  22460. }
  22461. }
  22462. /**
  22463. * Exit full window
  22464. *
  22465. * @fires Player#exitFullWindow
  22466. */
  22467. ;
  22468. _proto.exitFullWindow = function exitFullWindow() {
  22469. this.isFullWindow = false;
  22470. off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
  22471. document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
  22472. removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
  22473. // this.positionAll();
  22474. /**
  22475. * @event Player#exitFullWindow
  22476. * @type {EventTarget~Event}
  22477. */
  22478. this.trigger('exitFullWindow');
  22479. }
  22480. /**
  22481. * Check if the player is in Picture-in-Picture mode or tell the player that it
  22482. * is or is not in Picture-in-Picture mode.
  22483. *
  22484. * @param {boolean} [isPiP]
  22485. * Set the players current Picture-in-Picture state
  22486. *
  22487. * @return {boolean}
  22488. * - true if Picture-in-Picture is on and getting
  22489. * - false if Picture-in-Picture is off and getting
  22490. */
  22491. ;
  22492. _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) {
  22493. if (isPiP !== undefined) {
  22494. this.isInPictureInPicture_ = !!isPiP;
  22495. this.togglePictureInPictureClass_();
  22496. return;
  22497. }
  22498. return !!this.isInPictureInPicture_;
  22499. }
  22500. /**
  22501. * Create a floating video window always on top of other windows so that users may
  22502. * continue consuming media while they interact with other content sites, or
  22503. * applications on their device.
  22504. *
  22505. * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
  22506. *
  22507. * @fires Player#enterpictureinpicture
  22508. *
  22509. * @return {Promise}
  22510. * A promise with a Picture-in-Picture window.
  22511. */
  22512. ;
  22513. _proto.requestPictureInPicture = function requestPictureInPicture() {
  22514. if ('pictureInPictureEnabled' in document) {
  22515. /**
  22516. * This event fires when the player enters picture in picture mode
  22517. *
  22518. * @event Player#enterpictureinpicture
  22519. * @type {EventTarget~Event}
  22520. */
  22521. return this.techGet_('requestPictureInPicture');
  22522. }
  22523. }
  22524. /**
  22525. * Exit Picture-in-Picture mode.
  22526. *
  22527. * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
  22528. *
  22529. * @fires Player#leavepictureinpicture
  22530. *
  22531. * @return {Promise}
  22532. * A promise.
  22533. */
  22534. ;
  22535. _proto.exitPictureInPicture = function exitPictureInPicture() {
  22536. if ('pictureInPictureEnabled' in document) {
  22537. /**
  22538. * This event fires when the player leaves picture in picture mode
  22539. *
  22540. * @event Player#leavepictureinpicture
  22541. * @type {EventTarget~Event}
  22542. */
  22543. return document.exitPictureInPicture();
  22544. }
  22545. }
  22546. /**
  22547. * Called when this Player has focus and a key gets pressed down, or when
  22548. * any Component of this player receives a key press that it doesn't handle.
  22549. * This allows player-wide hotkeys (either as defined below, or optionally
  22550. * by an external function).
  22551. *
  22552. * @param {EventTarget~Event} event
  22553. * The `keydown` event that caused this function to be called.
  22554. *
  22555. * @listens keydown
  22556. */
  22557. ;
  22558. _proto.handleKeyDown = function handleKeyDown(event) {
  22559. var userActions = this.options_.userActions; // Bail out if hotkeys are not configured.
  22560. if (!userActions || !userActions.hotkeys) {
  22561. return;
  22562. } // Function that determines whether or not to exclude an element from
  22563. // hotkeys handling.
  22564. var excludeElement = function excludeElement(el) {
  22565. var tagName = el.tagName.toLowerCase(); // These tags will be excluded entirely.
  22566. var excludedTags = ['textarea']; // Inputs matching these types will still trigger hotkey handling as
  22567. // they are not text inputs.
  22568. var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
  22569. if (tagName === 'input') {
  22570. return allowedInputTypes.indexOf(el.type) === -1;
  22571. }
  22572. return excludedTags.indexOf(tagName) !== -1;
  22573. }; // Bail out if the user is focused on an interactive form element.
  22574. if (excludeElement(this.el_.ownerDocument.activeElement)) {
  22575. return;
  22576. }
  22577. if (typeof userActions.hotkeys === 'function') {
  22578. userActions.hotkeys.call(this, event);
  22579. } else {
  22580. this.handleHotkeys(event);
  22581. }
  22582. }
  22583. /**
  22584. * Called when this Player receives a hotkey keydown event.
  22585. * Supported player-wide hotkeys are:
  22586. *
  22587. * f - toggle fullscreen
  22588. * m - toggle mute
  22589. * k or Space - toggle play/pause
  22590. *
  22591. * @param {EventTarget~Event} event
  22592. * The `keydown` event that caused this function to be called.
  22593. */
  22594. ;
  22595. _proto.handleHotkeys = function handleHotkeys(event) {
  22596. var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
  22597. var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
  22598. fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
  22599. return keycode.isEventKey(keydownEvent, 'f');
  22600. } : _hotkeys$fullscreenKe,
  22601. _hotkeys$muteKey = hotkeys.muteKey,
  22602. muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
  22603. return keycode.isEventKey(keydownEvent, 'm');
  22604. } : _hotkeys$muteKey,
  22605. _hotkeys$playPauseKey = hotkeys.playPauseKey,
  22606. playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
  22607. return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
  22608. } : _hotkeys$playPauseKey;
  22609. if (fullscreenKey.call(this, event)) {
  22610. event.preventDefault();
  22611. event.stopPropagation();
  22612. var FSToggle = Component.getComponent('FullscreenToggle');
  22613. if (document[this.fsApi_.fullscreenEnabled] !== false) {
  22614. FSToggle.prototype.handleClick.call(this, event);
  22615. }
  22616. } else if (muteKey.call(this, event)) {
  22617. event.preventDefault();
  22618. event.stopPropagation();
  22619. var MuteToggle = Component.getComponent('MuteToggle');
  22620. MuteToggle.prototype.handleClick.call(this, event);
  22621. } else if (playPauseKey.call(this, event)) {
  22622. event.preventDefault();
  22623. event.stopPropagation();
  22624. var PlayToggle = Component.getComponent('PlayToggle');
  22625. PlayToggle.prototype.handleClick.call(this, event);
  22626. }
  22627. }
  22628. /**
  22629. * Check whether the player can play a given mimetype
  22630. *
  22631. * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
  22632. *
  22633. * @param {string} type
  22634. * The mimetype to check
  22635. *
  22636. * @return {string}
  22637. * 'probably', 'maybe', or '' (empty string)
  22638. */
  22639. ;
  22640. _proto.canPlayType = function canPlayType(type) {
  22641. var can; // Loop through each playback technology in the options order
  22642. for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
  22643. var techName = j[i];
  22644. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  22645. // Remove once that deprecated behavior is removed.
  22646. if (!tech) {
  22647. tech = Component.getComponent(techName);
  22648. } // Check if the current tech is defined before continuing
  22649. if (!tech) {
  22650. log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  22651. continue;
  22652. } // Check if the browser supports this technology
  22653. if (tech.isSupported()) {
  22654. can = tech.canPlayType(type);
  22655. if (can) {
  22656. return can;
  22657. }
  22658. }
  22659. }
  22660. return '';
  22661. }
  22662. /**
  22663. * Select source based on tech-order or source-order
  22664. * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
  22665. * defaults to tech-order selection
  22666. *
  22667. * @param {Array} sources
  22668. * The sources for a media asset
  22669. *
  22670. * @return {Object|boolean}
  22671. * Object of source and tech order or false
  22672. */
  22673. ;
  22674. _proto.selectSource = function selectSource(sources) {
  22675. var _this10 = this;
  22676. // Get only the techs specified in `techOrder` that exist and are supported by the
  22677. // current platform
  22678. var techs = this.options_.techOrder.map(function (techName) {
  22679. return [techName, Tech.getTech(techName)];
  22680. }).filter(function (_ref) {
  22681. var techName = _ref[0],
  22682. tech = _ref[1];
  22683. // Check if the current tech is defined before continuing
  22684. if (tech) {
  22685. // Check if the browser supports this technology
  22686. return tech.isSupported();
  22687. }
  22688. log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  22689. return false;
  22690. }); // Iterate over each `innerArray` element once per `outerArray` element and execute
  22691. // `tester` with both. If `tester` returns a non-falsy value, exit early and return
  22692. // that value.
  22693. var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
  22694. var found;
  22695. outerArray.some(function (outerChoice) {
  22696. return innerArray.some(function (innerChoice) {
  22697. found = tester(outerChoice, innerChoice);
  22698. if (found) {
  22699. return true;
  22700. }
  22701. });
  22702. });
  22703. return found;
  22704. };
  22705. var foundSourceAndTech;
  22706. var flip = function flip(fn) {
  22707. return function (a, b) {
  22708. return fn(b, a);
  22709. };
  22710. };
  22711. var finder = function finder(_ref2, source) {
  22712. var techName = _ref2[0],
  22713. tech = _ref2[1];
  22714. if (tech.canPlaySource(source, _this10.options_[techName.toLowerCase()])) {
  22715. return {
  22716. source: source,
  22717. tech: techName
  22718. };
  22719. }
  22720. }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
  22721. // to select from them based on their priority.
  22722. if (this.options_.sourceOrder) {
  22723. // Source-first ordering
  22724. foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
  22725. } else {
  22726. // Tech-first ordering
  22727. foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
  22728. }
  22729. return foundSourceAndTech || false;
  22730. }
  22731. /**
  22732. * Get or set the video source.
  22733. *
  22734. * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
  22735. * A SourceObject, an array of SourceObjects, or a string referencing
  22736. * a URL to a media source. It is _highly recommended_ that an object
  22737. * or array of objects is used here, so that source selection
  22738. * algorithms can take the `type` into account.
  22739. *
  22740. * If not provided, this method acts as a getter.
  22741. *
  22742. * @return {string|undefined}
  22743. * If the `source` argument is missing, returns the current source
  22744. * URL. Otherwise, returns nothing/undefined.
  22745. */
  22746. ;
  22747. _proto.src = function src(source) {
  22748. var _this11 = this;
  22749. // getter usage
  22750. if (typeof source === 'undefined') {
  22751. return this.cache_.src || '';
  22752. } // filter out invalid sources and turn our source into
  22753. // an array of source objects
  22754. var sources = filterSource(source); // if a source was passed in then it is invalid because
  22755. // it was filtered to a zero length Array. So we have to
  22756. // show an error
  22757. if (!sources.length) {
  22758. this.setTimeout(function () {
  22759. this.error({
  22760. code: 4,
  22761. message: this.localize(this.options_.notSupportedMessage)
  22762. });
  22763. }, 0);
  22764. return;
  22765. } // intial sources
  22766. this.changingSrc_ = true;
  22767. this.cache_.sources = sources;
  22768. this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
  22769. setSource(this, sources[0], function (middlewareSource, mws) {
  22770. _this11.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
  22771. // the source that is selected could be out of order from the cache update above this callback.
  22772. _this11.cache_.sources = sources;
  22773. _this11.updateSourceCaches_(middlewareSource);
  22774. var err = _this11.src_(middlewareSource);
  22775. if (err) {
  22776. if (sources.length > 1) {
  22777. return _this11.src(sources.slice(1));
  22778. }
  22779. _this11.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
  22780. _this11.setTimeout(function () {
  22781. this.error({
  22782. code: 4,
  22783. message: this.localize(this.options_.notSupportedMessage)
  22784. });
  22785. }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
  22786. // this needs a better comment about why this is needed
  22787. _this11.triggerReady();
  22788. return;
  22789. }
  22790. setTech(mws, _this11.tech_);
  22791. });
  22792. }
  22793. /**
  22794. * Set the source object on the tech, returns a boolean that indicates whether
  22795. * there is a tech that can play the source or not
  22796. *
  22797. * @param {Tech~SourceObject} source
  22798. * The source object to set on the Tech
  22799. *
  22800. * @return {boolean}
  22801. * - True if there is no Tech to playback this source
  22802. * - False otherwise
  22803. *
  22804. * @private
  22805. */
  22806. ;
  22807. _proto.src_ = function src_(source) {
  22808. var _this12 = this;
  22809. var sourceTech = this.selectSource([source]);
  22810. if (!sourceTech) {
  22811. return true;
  22812. }
  22813. if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
  22814. this.changingSrc_ = true; // load this technology with the chosen source
  22815. this.loadTech_(sourceTech.tech, sourceTech.source);
  22816. this.tech_.ready(function () {
  22817. _this12.changingSrc_ = false;
  22818. });
  22819. return false;
  22820. } // wait until the tech is ready to set the source
  22821. // and set it synchronously if possible (#2326)
  22822. this.ready(function () {
  22823. // The setSource tech method was added with source handlers
  22824. // so older techs won't support it
  22825. // We need to check the direct prototype for the case where subclasses
  22826. // of the tech do not support source handlers
  22827. if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
  22828. this.techCall_('setSource', source);
  22829. } else {
  22830. this.techCall_('src', source.src);
  22831. }
  22832. this.changingSrc_ = false;
  22833. }, true);
  22834. return false;
  22835. }
  22836. /**
  22837. * Begin loading the src data.
  22838. */
  22839. ;
  22840. _proto.load = function load() {
  22841. this.techCall_('load');
  22842. }
  22843. /**
  22844. * Reset the player. Loads the first tech in the techOrder,
  22845. * removes all the text tracks in the existing `tech`,
  22846. * and calls `reset` on the `tech`.
  22847. */
  22848. ;
  22849. _proto.reset = function reset() {
  22850. var _this13 = this;
  22851. var PromiseClass = this.options_.Promise || window$1.Promise;
  22852. if (this.paused() || !PromiseClass) {
  22853. this.doReset_();
  22854. } else {
  22855. var playPromise = this.play();
  22856. silencePromise(playPromise.then(function () {
  22857. return _this13.doReset_();
  22858. }));
  22859. }
  22860. };
  22861. _proto.doReset_ = function doReset_() {
  22862. if (this.tech_) {
  22863. this.tech_.clearTracks('text');
  22864. }
  22865. this.resetCache_();
  22866. this.poster('');
  22867. this.loadTech_(this.options_.techOrder[0], null);
  22868. this.techCall_('reset');
  22869. this.resetControlBarUI_();
  22870. if (isEvented(this)) {
  22871. this.trigger('playerreset');
  22872. }
  22873. }
  22874. /**
  22875. * Reset Control Bar's UI by calling sub-methods that reset
  22876. * all of Control Bar's components
  22877. */
  22878. ;
  22879. _proto.resetControlBarUI_ = function resetControlBarUI_() {
  22880. this.resetProgressBar_();
  22881. this.resetPlaybackRate_();
  22882. this.resetVolumeBar_();
  22883. }
  22884. /**
  22885. * Reset tech's progress so progress bar is reset in the UI
  22886. */
  22887. ;
  22888. _proto.resetProgressBar_ = function resetProgressBar_() {
  22889. this.currentTime(0);
  22890. var _this$controlBar = this.controlBar,
  22891. durationDisplay = _this$controlBar.durationDisplay,
  22892. remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
  22893. if (durationDisplay) {
  22894. durationDisplay.updateContent();
  22895. }
  22896. if (remainingTimeDisplay) {
  22897. remainingTimeDisplay.updateContent();
  22898. }
  22899. }
  22900. /**
  22901. * Reset Playback ratio
  22902. */
  22903. ;
  22904. _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
  22905. this.playbackRate(this.defaultPlaybackRate());
  22906. this.handleTechRateChange_();
  22907. }
  22908. /**
  22909. * Reset Volume bar
  22910. */
  22911. ;
  22912. _proto.resetVolumeBar_ = function resetVolumeBar_() {
  22913. this.volume(1.0);
  22914. this.trigger('volumechange');
  22915. }
  22916. /**
  22917. * Returns all of the current source objects.
  22918. *
  22919. * @return {Tech~SourceObject[]}
  22920. * The current source objects
  22921. */
  22922. ;
  22923. _proto.currentSources = function currentSources() {
  22924. var source = this.currentSource();
  22925. var sources = []; // assume `{}` or `{ src }`
  22926. if (Object.keys(source).length !== 0) {
  22927. sources.push(source);
  22928. }
  22929. return this.cache_.sources || sources;
  22930. }
  22931. /**
  22932. * Returns the current source object.
  22933. *
  22934. * @return {Tech~SourceObject}
  22935. * The current source object
  22936. */
  22937. ;
  22938. _proto.currentSource = function currentSource() {
  22939. return this.cache_.source || {};
  22940. }
  22941. /**
  22942. * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
  22943. * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
  22944. *
  22945. * @return {string}
  22946. * The current source
  22947. */
  22948. ;
  22949. _proto.currentSrc = function currentSrc() {
  22950. return this.currentSource() && this.currentSource().src || '';
  22951. }
  22952. /**
  22953. * Get the current source type e.g. video/mp4
  22954. * This can allow you rebuild the current source object so that you could load the same
  22955. * source and tech later
  22956. *
  22957. * @return {string}
  22958. * The source MIME type
  22959. */
  22960. ;
  22961. _proto.currentType = function currentType() {
  22962. return this.currentSource() && this.currentSource().type || '';
  22963. }
  22964. /**
  22965. * Get or set the preload attribute
  22966. *
  22967. * @param {boolean} [value]
  22968. * - true means that we should preload
  22969. * - false means that we should not preload
  22970. *
  22971. * @return {string}
  22972. * The preload attribute value when getting
  22973. */
  22974. ;
  22975. _proto.preload = function preload(value) {
  22976. if (value !== undefined) {
  22977. this.techCall_('setPreload', value);
  22978. this.options_.preload = value;
  22979. return;
  22980. }
  22981. return this.techGet_('preload');
  22982. }
  22983. /**
  22984. * Get or set the autoplay option. When this is a boolean it will
  22985. * modify the attribute on the tech. When this is a string the attribute on
  22986. * the tech will be removed and `Player` will handle autoplay on loadstarts.
  22987. *
  22988. * @param {boolean|string} [value]
  22989. * - true: autoplay using the browser behavior
  22990. * - false: do not autoplay
  22991. * - 'play': call play() on every loadstart
  22992. * - 'muted': call muted() then play() on every loadstart
  22993. * - 'any': call play() on every loadstart. if that fails call muted() then play().
  22994. * - *: values other than those listed here will be set `autoplay` to true
  22995. *
  22996. * @return {boolean|string}
  22997. * The current value of autoplay when getting
  22998. */
  22999. ;
  23000. _proto.autoplay = function autoplay(value) {
  23001. // getter usage
  23002. if (value === undefined) {
  23003. return this.options_.autoplay || false;
  23004. }
  23005. var techAutoplay; // if the value is a valid string set it to that
  23006. if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
  23007. this.options_.autoplay = value;
  23008. this.manualAutoplay_(value);
  23009. techAutoplay = false; // any falsy value sets autoplay to false in the browser,
  23010. // lets do the same
  23011. } else if (!value) {
  23012. this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
  23013. } else {
  23014. this.options_.autoplay = true;
  23015. }
  23016. techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
  23017. // a setAutoplay call on tech ready. We do this because the
  23018. // autoplay option will be passed in the constructor and we
  23019. // do not need to set it twice
  23020. if (this.tech_) {
  23021. this.techCall_('setAutoplay', techAutoplay);
  23022. }
  23023. }
  23024. /**
  23025. * Set or unset the playsinline attribute.
  23026. * Playsinline tells the browser that non-fullscreen playback is preferred.
  23027. *
  23028. * @param {boolean} [value]
  23029. * - true means that we should try to play inline by default
  23030. * - false means that we should use the browser's default playback mode,
  23031. * which in most cases is inline. iOS Safari is a notable exception
  23032. * and plays fullscreen by default.
  23033. *
  23034. * @return {string|Player}
  23035. * - the current value of playsinline
  23036. * - the player when setting
  23037. *
  23038. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  23039. */
  23040. ;
  23041. _proto.playsinline = function playsinline(value) {
  23042. if (value !== undefined) {
  23043. this.techCall_('setPlaysinline', value);
  23044. this.options_.playsinline = value;
  23045. return this;
  23046. }
  23047. return this.techGet_('playsinline');
  23048. }
  23049. /**
  23050. * Get or set the loop attribute on the video element.
  23051. *
  23052. * @param {boolean} [value]
  23053. * - true means that we should loop the video
  23054. * - false means that we should not loop the video
  23055. *
  23056. * @return {boolean}
  23057. * The current value of loop when getting
  23058. */
  23059. ;
  23060. _proto.loop = function loop(value) {
  23061. if (value !== undefined) {
  23062. this.techCall_('setLoop', value);
  23063. this.options_.loop = value;
  23064. return;
  23065. }
  23066. return this.techGet_('loop');
  23067. }
  23068. /**
  23069. * Get or set the poster image source url
  23070. *
  23071. * @fires Player#posterchange
  23072. *
  23073. * @param {string} [src]
  23074. * Poster image source URL
  23075. *
  23076. * @return {string}
  23077. * The current value of poster when getting
  23078. */
  23079. ;
  23080. _proto.poster = function poster(src) {
  23081. if (src === undefined) {
  23082. return this.poster_;
  23083. } // The correct way to remove a poster is to set as an empty string
  23084. // other falsey values will throw errors
  23085. if (!src) {
  23086. src = '';
  23087. }
  23088. if (src === this.poster_) {
  23089. return;
  23090. } // update the internal poster variable
  23091. this.poster_ = src; // update the tech's poster
  23092. this.techCall_('setPoster', src);
  23093. this.isPosterFromTech_ = false; // alert components that the poster has been set
  23094. /**
  23095. * This event fires when the poster image is changed on the player.
  23096. *
  23097. * @event Player#posterchange
  23098. * @type {EventTarget~Event}
  23099. */
  23100. this.trigger('posterchange');
  23101. }
  23102. /**
  23103. * Some techs (e.g. YouTube) can provide a poster source in an
  23104. * asynchronous way. We want the poster component to use this
  23105. * poster source so that it covers up the tech's controls.
  23106. * (YouTube's play button). However we only want to use this
  23107. * source if the player user hasn't set a poster through
  23108. * the normal APIs.
  23109. *
  23110. * @fires Player#posterchange
  23111. * @listens Tech#posterchange
  23112. * @private
  23113. */
  23114. ;
  23115. _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
  23116. if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
  23117. var newPoster = this.tech_.poster() || '';
  23118. if (newPoster !== this.poster_) {
  23119. this.poster_ = newPoster;
  23120. this.isPosterFromTech_ = true; // Let components know the poster has changed
  23121. this.trigger('posterchange');
  23122. }
  23123. }
  23124. }
  23125. /**
  23126. * Get or set whether or not the controls are showing.
  23127. *
  23128. * @fires Player#controlsenabled
  23129. *
  23130. * @param {boolean} [bool]
  23131. * - true to turn controls on
  23132. * - false to turn controls off
  23133. *
  23134. * @return {boolean}
  23135. * The current value of controls when getting
  23136. */
  23137. ;
  23138. _proto.controls = function controls(bool) {
  23139. if (bool === undefined) {
  23140. return !!this.controls_;
  23141. }
  23142. bool = !!bool; // Don't trigger a change event unless it actually changed
  23143. if (this.controls_ === bool) {
  23144. return;
  23145. }
  23146. this.controls_ = bool;
  23147. if (this.usingNativeControls()) {
  23148. this.techCall_('setControls', bool);
  23149. }
  23150. if (this.controls_) {
  23151. this.removeClass('vjs-controls-disabled');
  23152. this.addClass('vjs-controls-enabled');
  23153. /**
  23154. * @event Player#controlsenabled
  23155. * @type {EventTarget~Event}
  23156. */
  23157. this.trigger('controlsenabled');
  23158. if (!this.usingNativeControls()) {
  23159. this.addTechControlsListeners_();
  23160. }
  23161. } else {
  23162. this.removeClass('vjs-controls-enabled');
  23163. this.addClass('vjs-controls-disabled');
  23164. /**
  23165. * @event Player#controlsdisabled
  23166. * @type {EventTarget~Event}
  23167. */
  23168. this.trigger('controlsdisabled');
  23169. if (!this.usingNativeControls()) {
  23170. this.removeTechControlsListeners_();
  23171. }
  23172. }
  23173. }
  23174. /**
  23175. * Toggle native controls on/off. Native controls are the controls built into
  23176. * devices (e.g. default iPhone controls), Flash, or other techs
  23177. * (e.g. Vimeo Controls)
  23178. * **This should only be set by the current tech, because only the tech knows
  23179. * if it can support native controls**
  23180. *
  23181. * @fires Player#usingnativecontrols
  23182. * @fires Player#usingcustomcontrols
  23183. *
  23184. * @param {boolean} [bool]
  23185. * - true to turn native controls on
  23186. * - false to turn native controls off
  23187. *
  23188. * @return {boolean}
  23189. * The current value of native controls when getting
  23190. */
  23191. ;
  23192. _proto.usingNativeControls = function usingNativeControls(bool) {
  23193. if (bool === undefined) {
  23194. return !!this.usingNativeControls_;
  23195. }
  23196. bool = !!bool; // Don't trigger a change event unless it actually changed
  23197. if (this.usingNativeControls_ === bool) {
  23198. return;
  23199. }
  23200. this.usingNativeControls_ = bool;
  23201. if (this.usingNativeControls_) {
  23202. this.addClass('vjs-using-native-controls');
  23203. /**
  23204. * player is using the native device controls
  23205. *
  23206. * @event Player#usingnativecontrols
  23207. * @type {EventTarget~Event}
  23208. */
  23209. this.trigger('usingnativecontrols');
  23210. } else {
  23211. this.removeClass('vjs-using-native-controls');
  23212. /**
  23213. * player is using the custom HTML controls
  23214. *
  23215. * @event Player#usingcustomcontrols
  23216. * @type {EventTarget~Event}
  23217. */
  23218. this.trigger('usingcustomcontrols');
  23219. }
  23220. }
  23221. /**
  23222. * Set or get the current MediaError
  23223. *
  23224. * @fires Player#error
  23225. *
  23226. * @param {MediaError|string|number} [err]
  23227. * A MediaError or a string/number to be turned
  23228. * into a MediaError
  23229. *
  23230. * @return {MediaError|null}
  23231. * The current MediaError when getting (or null)
  23232. */
  23233. ;
  23234. _proto.error = function error(err) {
  23235. if (err === undefined) {
  23236. return this.error_ || null;
  23237. } // Suppress the first error message for no compatible source until
  23238. // user interaction
  23239. if (this.options_.suppressNotSupportedError && err && err.message && err.message === this.localize(this.options_.notSupportedMessage)) {
  23240. var triggerSuppressedError = function triggerSuppressedError() {
  23241. this.error(err);
  23242. };
  23243. this.options_.suppressNotSupportedError = false;
  23244. this.any(['click', 'touchstart'], triggerSuppressedError);
  23245. this.one('loadstart', function () {
  23246. this.off(['click', 'touchstart'], triggerSuppressedError);
  23247. });
  23248. return;
  23249. } // restoring to default
  23250. if (err === null) {
  23251. this.error_ = err;
  23252. this.removeClass('vjs-error');
  23253. if (this.errorDisplay) {
  23254. this.errorDisplay.close();
  23255. }
  23256. return;
  23257. }
  23258. this.error_ = new MediaError(err); // add the vjs-error classname to the player
  23259. this.addClass('vjs-error'); // log the name of the error type and any message
  23260. // IE11 logs "[object object]" and required you to expand message to see error object
  23261. log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
  23262. /**
  23263. * @event Player#error
  23264. * @type {EventTarget~Event}
  23265. */
  23266. this.trigger('error');
  23267. return;
  23268. }
  23269. /**
  23270. * Report user activity
  23271. *
  23272. * @param {Object} event
  23273. * Event object
  23274. */
  23275. ;
  23276. _proto.reportUserActivity = function reportUserActivity(event) {
  23277. this.userActivity_ = true;
  23278. }
  23279. /**
  23280. * Get/set if user is active
  23281. *
  23282. * @fires Player#useractive
  23283. * @fires Player#userinactive
  23284. *
  23285. * @param {boolean} [bool]
  23286. * - true if the user is active
  23287. * - false if the user is inactive
  23288. *
  23289. * @return {boolean}
  23290. * The current value of userActive when getting
  23291. */
  23292. ;
  23293. _proto.userActive = function userActive(bool) {
  23294. if (bool === undefined) {
  23295. return this.userActive_;
  23296. }
  23297. bool = !!bool;
  23298. if (bool === this.userActive_) {
  23299. return;
  23300. }
  23301. this.userActive_ = bool;
  23302. if (this.userActive_) {
  23303. this.userActivity_ = true;
  23304. this.removeClass('vjs-user-inactive');
  23305. this.addClass('vjs-user-active');
  23306. /**
  23307. * @event Player#useractive
  23308. * @type {EventTarget~Event}
  23309. */
  23310. this.trigger('useractive');
  23311. return;
  23312. } // Chrome/Safari/IE have bugs where when you change the cursor it can
  23313. // trigger a mousemove event. This causes an issue when you're hiding
  23314. // the cursor when the user is inactive, and a mousemove signals user
  23315. // activity. Making it impossible to go into inactive mode. Specifically
  23316. // this happens in fullscreen when we really need to hide the cursor.
  23317. //
  23318. // When this gets resolved in ALL browsers it can be removed
  23319. // https://code.google.com/p/chromium/issues/detail?id=103041
  23320. if (this.tech_) {
  23321. this.tech_.one('mousemove', function (e) {
  23322. e.stopPropagation();
  23323. e.preventDefault();
  23324. });
  23325. }
  23326. this.userActivity_ = false;
  23327. this.removeClass('vjs-user-active');
  23328. this.addClass('vjs-user-inactive');
  23329. /**
  23330. * @event Player#userinactive
  23331. * @type {EventTarget~Event}
  23332. */
  23333. this.trigger('userinactive');
  23334. }
  23335. /**
  23336. * Listen for user activity based on timeout value
  23337. *
  23338. * @private
  23339. */
  23340. ;
  23341. _proto.listenForUserActivity_ = function listenForUserActivity_() {
  23342. var mouseInProgress;
  23343. var lastMoveX;
  23344. var lastMoveY;
  23345. var handleActivity = bind(this, this.reportUserActivity);
  23346. var handleMouseMove = function handleMouseMove(e) {
  23347. // #1068 - Prevent mousemove spamming
  23348. // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
  23349. if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
  23350. lastMoveX = e.screenX;
  23351. lastMoveY = e.screenY;
  23352. handleActivity();
  23353. }
  23354. };
  23355. var handleMouseDown = function handleMouseDown() {
  23356. handleActivity(); // For as long as the they are touching the device or have their mouse down,
  23357. // we consider them active even if they're not moving their finger or mouse.
  23358. // So we want to continue to update that they are active
  23359. this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
  23360. // as the activityCheck interval (250) should ensure we never miss the
  23361. // next activityCheck
  23362. mouseInProgress = this.setInterval(handleActivity, 250);
  23363. };
  23364. var handleMouseUp = function handleMouseUp(event) {
  23365. handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
  23366. this.clearInterval(mouseInProgress);
  23367. }; // Any mouse movement will be considered user activity
  23368. this.on('mousedown', handleMouseDown);
  23369. this.on('mousemove', handleMouseMove);
  23370. this.on('mouseup', handleMouseUp);
  23371. var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
  23372. // controlBar would no longer be hidden by default timeout.
  23373. if (controlBar && !IS_IOS && !IS_ANDROID) {
  23374. controlBar.on('mouseenter', function (event) {
  23375. this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
  23376. this.player().options_.inactivityTimeout = 0;
  23377. });
  23378. controlBar.on('mouseleave', function (event) {
  23379. this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
  23380. });
  23381. } // Listen for keyboard navigation
  23382. // Shouldn't need to use inProgress interval because of key repeat
  23383. this.on('keydown', handleActivity);
  23384. this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
  23385. // the mousemove/touchmove function itself, to prevent performance degradation.
  23386. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  23387. // then gets picked up by this loop
  23388. // http://ejohn.org/blog/learning-from-twitter/
  23389. var inactivityTimeout;
  23390. this.setInterval(function () {
  23391. // Check to see if mouse/touch activity has happened
  23392. if (!this.userActivity_) {
  23393. return;
  23394. } // Reset the activity tracker
  23395. this.userActivity_ = false; // If the user state was inactive, set the state to active
  23396. this.userActive(true); // Clear any existing inactivity timeout to start the timer over
  23397. this.clearTimeout(inactivityTimeout);
  23398. var timeout = this.options_.inactivityTimeout;
  23399. if (timeout <= 0) {
  23400. return;
  23401. } // In <timeout> milliseconds, if no more activity has occurred the
  23402. // user will be considered inactive
  23403. inactivityTimeout = this.setTimeout(function () {
  23404. // Protect against the case where the inactivityTimeout can trigger just
  23405. // before the next user activity is picked up by the activity check loop
  23406. // causing a flicker
  23407. if (!this.userActivity_) {
  23408. this.userActive(false);
  23409. }
  23410. }, timeout);
  23411. }, 250);
  23412. }
  23413. /**
  23414. * Gets or sets the current playback rate. A playback rate of
  23415. * 1.0 represents normal speed and 0.5 would indicate half-speed
  23416. * playback, for instance.
  23417. *
  23418. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
  23419. *
  23420. * @param {number} [rate]
  23421. * New playback rate to set.
  23422. *
  23423. * @return {number}
  23424. * The current playback rate when getting or 1.0
  23425. */
  23426. ;
  23427. _proto.playbackRate = function playbackRate(rate) {
  23428. if (rate !== undefined) {
  23429. // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
  23430. // that is registered above
  23431. this.techCall_('setPlaybackRate', rate);
  23432. return;
  23433. }
  23434. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  23435. return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
  23436. }
  23437. return 1.0;
  23438. }
  23439. /**
  23440. * Gets or sets the current default playback rate. A default playback rate of
  23441. * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
  23442. * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
  23443. * not the current playbackRate.
  23444. *
  23445. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
  23446. *
  23447. * @param {number} [rate]
  23448. * New default playback rate to set.
  23449. *
  23450. * @return {number|Player}
  23451. * - The default playback rate when getting or 1.0
  23452. * - the player when setting
  23453. */
  23454. ;
  23455. _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
  23456. if (rate !== undefined) {
  23457. return this.techCall_('setDefaultPlaybackRate', rate);
  23458. }
  23459. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  23460. return this.techGet_('defaultPlaybackRate');
  23461. }
  23462. return 1.0;
  23463. }
  23464. /**
  23465. * Gets or sets the audio flag
  23466. *
  23467. * @param {boolean} bool
  23468. * - true signals that this is an audio player
  23469. * - false signals that this is not an audio player
  23470. *
  23471. * @return {boolean}
  23472. * The current value of isAudio when getting
  23473. */
  23474. ;
  23475. _proto.isAudio = function isAudio(bool) {
  23476. if (bool !== undefined) {
  23477. this.isAudio_ = !!bool;
  23478. return;
  23479. }
  23480. return !!this.isAudio_;
  23481. }
  23482. /**
  23483. * A helper method for adding a {@link TextTrack} to our
  23484. * {@link TextTrackList}.
  23485. *
  23486. * In addition to the W3C settings we allow adding additional info through options.
  23487. *
  23488. * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  23489. *
  23490. * @param {string} [kind]
  23491. * the kind of TextTrack you are adding
  23492. *
  23493. * @param {string} [label]
  23494. * the label to give the TextTrack label
  23495. *
  23496. * @param {string} [language]
  23497. * the language to set on the TextTrack
  23498. *
  23499. * @return {TextTrack|undefined}
  23500. * the TextTrack that was added or undefined
  23501. * if there is no tech
  23502. */
  23503. ;
  23504. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  23505. if (this.tech_) {
  23506. return this.tech_.addTextTrack(kind, label, language);
  23507. }
  23508. }
  23509. /**
  23510. * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
  23511. * automatically removed from the video element whenever the source changes, unless
  23512. * manualCleanup is set to false.
  23513. *
  23514. * @param {Object} options
  23515. * Options to pass to {@link HTMLTrackElement} during creation. See
  23516. * {@link HTMLTrackElement} for object properties that you should use.
  23517. *
  23518. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  23519. *
  23520. * @return {HtmlTrackElement}
  23521. * the HTMLTrackElement that was created and added
  23522. * to the HtmlTrackElementList and the remote
  23523. * TextTrackList
  23524. *
  23525. * @deprecated The default value of the "manualCleanup" parameter will default
  23526. * to "false" in upcoming versions of Video.js
  23527. */
  23528. ;
  23529. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  23530. if (this.tech_) {
  23531. return this.tech_.addRemoteTextTrack(options, manualCleanup);
  23532. }
  23533. }
  23534. /**
  23535. * Remove a remote {@link TextTrack} from the respective
  23536. * {@link TextTrackList} and {@link HtmlTrackElementList}.
  23537. *
  23538. * @param {Object} track
  23539. * Remote {@link TextTrack} to remove
  23540. *
  23541. * @return {undefined}
  23542. * does not return anything
  23543. */
  23544. ;
  23545. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
  23546. if (obj === void 0) {
  23547. obj = {};
  23548. }
  23549. var _obj = obj,
  23550. track = _obj.track;
  23551. if (!track) {
  23552. track = obj;
  23553. } // destructure the input into an object with a track argument, defaulting to arguments[0]
  23554. // default the whole argument to an empty object if nothing was passed in
  23555. if (this.tech_) {
  23556. return this.tech_.removeRemoteTextTrack(track);
  23557. }
  23558. }
  23559. /**
  23560. * Gets available media playback quality metrics as specified by the W3C's Media
  23561. * Playback Quality API.
  23562. *
  23563. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  23564. *
  23565. * @return {Object|undefined}
  23566. * An object with supported media playback quality metrics or undefined if there
  23567. * is no tech or the tech does not support it.
  23568. */
  23569. ;
  23570. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  23571. return this.techGet_('getVideoPlaybackQuality');
  23572. }
  23573. /**
  23574. * Get video width
  23575. *
  23576. * @return {number}
  23577. * current video width
  23578. */
  23579. ;
  23580. _proto.videoWidth = function videoWidth() {
  23581. return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
  23582. }
  23583. /**
  23584. * Get video height
  23585. *
  23586. * @return {number}
  23587. * current video height
  23588. */
  23589. ;
  23590. _proto.videoHeight = function videoHeight() {
  23591. return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
  23592. }
  23593. /**
  23594. * The player's language code
  23595. * NOTE: The language should be set in the player options if you want the
  23596. * the controls to be built with a specific language. Changing the language
  23597. * later will not update controls text.
  23598. *
  23599. * @param {string} [code]
  23600. * the language code to set the player to
  23601. *
  23602. * @return {string}
  23603. * The current language code when getting
  23604. */
  23605. ;
  23606. _proto.language = function language(code) {
  23607. if (code === undefined) {
  23608. return this.language_;
  23609. }
  23610. this.language_ = String(code).toLowerCase();
  23611. }
  23612. /**
  23613. * Get the player's language dictionary
  23614. * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
  23615. * Languages specified directly in the player options have precedence
  23616. *
  23617. * @return {Array}
  23618. * An array of of supported languages
  23619. */
  23620. ;
  23621. _proto.languages = function languages() {
  23622. return mergeOptions(Player.prototype.options_.languages, this.languages_);
  23623. }
  23624. /**
  23625. * returns a JavaScript object reperesenting the current track
  23626. * information. **DOES not return it as JSON**
  23627. *
  23628. * @return {Object}
  23629. * Object representing the current of track info
  23630. */
  23631. ;
  23632. _proto.toJSON = function toJSON() {
  23633. var options = mergeOptions(this.options_);
  23634. var tracks = options.tracks;
  23635. options.tracks = [];
  23636. for (var i = 0; i < tracks.length; i++) {
  23637. var track = tracks[i]; // deep merge tracks and null out player so no circular references
  23638. track = mergeOptions(track);
  23639. track.player = undefined;
  23640. options.tracks[i] = track;
  23641. }
  23642. return options;
  23643. }
  23644. /**
  23645. * Creates a simple modal dialog (an instance of the {@link ModalDialog}
  23646. * component) that immediately overlays the player with arbitrary
  23647. * content and removes itself when closed.
  23648. *
  23649. * @param {string|Function|Element|Array|null} content
  23650. * Same as {@link ModalDialog#content}'s param of the same name.
  23651. * The most straight-forward usage is to provide a string or DOM
  23652. * element.
  23653. *
  23654. * @param {Object} [options]
  23655. * Extra options which will be passed on to the {@link ModalDialog}.
  23656. *
  23657. * @return {ModalDialog}
  23658. * the {@link ModalDialog} that was created
  23659. */
  23660. ;
  23661. _proto.createModal = function createModal(content, options) {
  23662. var _this14 = this;
  23663. options = options || {};
  23664. options.content = content || '';
  23665. var modal = new ModalDialog(this, options);
  23666. this.addChild(modal);
  23667. modal.on('dispose', function () {
  23668. _this14.removeChild(modal);
  23669. });
  23670. modal.open();
  23671. return modal;
  23672. }
  23673. /**
  23674. * Change breakpoint classes when the player resizes.
  23675. *
  23676. * @private
  23677. */
  23678. ;
  23679. _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
  23680. if (!this.responsive()) {
  23681. return;
  23682. }
  23683. var currentBreakpoint = this.currentBreakpoint();
  23684. var currentWidth = this.currentWidth();
  23685. for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
  23686. var candidateBreakpoint = BREAKPOINT_ORDER[i];
  23687. var maxWidth = this.breakpoints_[candidateBreakpoint];
  23688. if (currentWidth <= maxWidth) {
  23689. // The current breakpoint did not change, nothing to do.
  23690. if (currentBreakpoint === candidateBreakpoint) {
  23691. return;
  23692. } // Only remove a class if there is a current breakpoint.
  23693. if (currentBreakpoint) {
  23694. this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
  23695. }
  23696. this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
  23697. this.breakpoint_ = candidateBreakpoint;
  23698. break;
  23699. }
  23700. }
  23701. }
  23702. /**
  23703. * Removes the current breakpoint.
  23704. *
  23705. * @private
  23706. */
  23707. ;
  23708. _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
  23709. var className = this.currentBreakpointClass();
  23710. this.breakpoint_ = '';
  23711. if (className) {
  23712. this.removeClass(className);
  23713. }
  23714. }
  23715. /**
  23716. * Get or set breakpoints on the player.
  23717. *
  23718. * Calling this method with an object or `true` will remove any previous
  23719. * custom breakpoints and start from the defaults again.
  23720. *
  23721. * @param {Object|boolean} [breakpoints]
  23722. * If an object is given, it can be used to provide custom
  23723. * breakpoints. If `true` is given, will set default breakpoints.
  23724. * If this argument is not given, will simply return the current
  23725. * breakpoints.
  23726. *
  23727. * @param {number} [breakpoints.tiny]
  23728. * The maximum width for the "vjs-layout-tiny" class.
  23729. *
  23730. * @param {number} [breakpoints.xsmall]
  23731. * The maximum width for the "vjs-layout-x-small" class.
  23732. *
  23733. * @param {number} [breakpoints.small]
  23734. * The maximum width for the "vjs-layout-small" class.
  23735. *
  23736. * @param {number} [breakpoints.medium]
  23737. * The maximum width for the "vjs-layout-medium" class.
  23738. *
  23739. * @param {number} [breakpoints.large]
  23740. * The maximum width for the "vjs-layout-large" class.
  23741. *
  23742. * @param {number} [breakpoints.xlarge]
  23743. * The maximum width for the "vjs-layout-x-large" class.
  23744. *
  23745. * @param {number} [breakpoints.huge]
  23746. * The maximum width for the "vjs-layout-huge" class.
  23747. *
  23748. * @return {Object}
  23749. * An object mapping breakpoint names to maximum width values.
  23750. */
  23751. ;
  23752. _proto.breakpoints = function breakpoints(_breakpoints) {
  23753. // Used as a getter.
  23754. if (_breakpoints === undefined) {
  23755. return assign(this.breakpoints_);
  23756. }
  23757. this.breakpoint_ = '';
  23758. this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
  23759. // selected breakpoint.
  23760. this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
  23761. return assign(this.breakpoints_);
  23762. }
  23763. /**
  23764. * Get or set a flag indicating whether or not this player should adjust
  23765. * its UI based on its dimensions.
  23766. *
  23767. * @param {boolean} value
  23768. * Should be `true` if the player should adjust its UI based on its
  23769. * dimensions; otherwise, should be `false`.
  23770. *
  23771. * @return {boolean}
  23772. * Will be `true` if this player should adjust its UI based on its
  23773. * dimensions; otherwise, will be `false`.
  23774. */
  23775. ;
  23776. _proto.responsive = function responsive(value) {
  23777. // Used as a getter.
  23778. if (value === undefined) {
  23779. return this.responsive_;
  23780. }
  23781. value = Boolean(value);
  23782. var current = this.responsive_; // Nothing changed.
  23783. if (value === current) {
  23784. return;
  23785. } // The value actually changed, set it.
  23786. this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
  23787. // player is now responsive.
  23788. if (value) {
  23789. this.on('playerresize', this.updateCurrentBreakpoint_);
  23790. this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
  23791. } else {
  23792. this.off('playerresize', this.updateCurrentBreakpoint_);
  23793. this.removeCurrentBreakpoint_();
  23794. }
  23795. return value;
  23796. }
  23797. /**
  23798. * Get current breakpoint name, if any.
  23799. *
  23800. * @return {string}
  23801. * If there is currently a breakpoint set, returns a the key from the
  23802. * breakpoints object matching it. Otherwise, returns an empty string.
  23803. */
  23804. ;
  23805. _proto.currentBreakpoint = function currentBreakpoint() {
  23806. return this.breakpoint_;
  23807. }
  23808. /**
  23809. * Get the current breakpoint class name.
  23810. *
  23811. * @return {string}
  23812. * The matching class name (e.g. `"vjs-layout-tiny"` or
  23813. * `"vjs-layout-large"`) for the current breakpoint. Empty string if
  23814. * there is no current breakpoint.
  23815. */
  23816. ;
  23817. _proto.currentBreakpointClass = function currentBreakpointClass() {
  23818. return BREAKPOINT_CLASSES[this.breakpoint_] || '';
  23819. }
  23820. /**
  23821. * An object that describes a single piece of media.
  23822. *
  23823. * Properties that are not part of this type description will be retained; so,
  23824. * this can be viewed as a generic metadata storage mechanism as well.
  23825. *
  23826. * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
  23827. * @typedef {Object} Player~MediaObject
  23828. *
  23829. * @property {string} [album]
  23830. * Unused, except if this object is passed to the `MediaSession`
  23831. * API.
  23832. *
  23833. * @property {string} [artist]
  23834. * Unused, except if this object is passed to the `MediaSession`
  23835. * API.
  23836. *
  23837. * @property {Object[]} [artwork]
  23838. * Unused, except if this object is passed to the `MediaSession`
  23839. * API. If not specified, will be populated via the `poster`, if
  23840. * available.
  23841. *
  23842. * @property {string} [poster]
  23843. * URL to an image that will display before playback.
  23844. *
  23845. * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
  23846. * A single source object, an array of source objects, or a string
  23847. * referencing a URL to a media source. It is _highly recommended_
  23848. * that an object or array of objects is used here, so that source
  23849. * selection algorithms can take the `type` into account.
  23850. *
  23851. * @property {string} [title]
  23852. * Unused, except if this object is passed to the `MediaSession`
  23853. * API.
  23854. *
  23855. * @property {Object[]} [textTracks]
  23856. * An array of objects to be used to create text tracks, following
  23857. * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
  23858. * For ease of removal, these will be created as "remote" text
  23859. * tracks and set to automatically clean up on source changes.
  23860. *
  23861. * These objects may have properties like `src`, `kind`, `label`,
  23862. * and `language`, see {@link Tech#createRemoteTextTrack}.
  23863. */
  23864. /**
  23865. * Populate the player using a {@link Player~MediaObject|MediaObject}.
  23866. *
  23867. * @param {Player~MediaObject} media
  23868. * A media object.
  23869. *
  23870. * @param {Function} ready
  23871. * A callback to be called when the player is ready.
  23872. */
  23873. ;
  23874. _proto.loadMedia = function loadMedia(media, ready) {
  23875. var _this15 = this;
  23876. if (!media || typeof media !== 'object') {
  23877. return;
  23878. }
  23879. this.reset(); // Clone the media object so it cannot be mutated from outside.
  23880. this.cache_.media = mergeOptions(media);
  23881. var _this$cache_$media = this.cache_.media,
  23882. artwork = _this$cache_$media.artwork,
  23883. poster = _this$cache_$media.poster,
  23884. src = _this$cache_$media.src,
  23885. textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
  23886. if (!artwork && poster) {
  23887. this.cache_.media.artwork = [{
  23888. src: poster,
  23889. type: getMimetype(poster)
  23890. }];
  23891. }
  23892. if (src) {
  23893. this.src(src);
  23894. }
  23895. if (poster) {
  23896. this.poster(poster);
  23897. }
  23898. if (Array.isArray(textTracks)) {
  23899. textTracks.forEach(function (tt) {
  23900. return _this15.addRemoteTextTrack(tt, false);
  23901. });
  23902. }
  23903. this.ready(ready);
  23904. }
  23905. /**
  23906. * Get a clone of the current {@link Player~MediaObject} for this player.
  23907. *
  23908. * If the `loadMedia` method has not been used, will attempt to return a
  23909. * {@link Player~MediaObject} based on the current state of the player.
  23910. *
  23911. * @return {Player~MediaObject}
  23912. */
  23913. ;
  23914. _proto.getMedia = function getMedia() {
  23915. if (!this.cache_.media) {
  23916. var poster = this.poster();
  23917. var src = this.currentSources();
  23918. var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
  23919. return {
  23920. kind: tt.kind,
  23921. label: tt.label,
  23922. language: tt.language,
  23923. src: tt.src
  23924. };
  23925. });
  23926. var media = {
  23927. src: src,
  23928. textTracks: textTracks
  23929. };
  23930. if (poster) {
  23931. media.poster = poster;
  23932. media.artwork = [{
  23933. src: media.poster,
  23934. type: getMimetype(media.poster)
  23935. }];
  23936. }
  23937. return media;
  23938. }
  23939. return mergeOptions(this.cache_.media);
  23940. }
  23941. /**
  23942. * Gets tag settings
  23943. *
  23944. * @param {Element} tag
  23945. * The player tag
  23946. *
  23947. * @return {Object}
  23948. * An object containing all of the settings
  23949. * for a player tag
  23950. */
  23951. ;
  23952. Player.getTagSettings = function getTagSettings(tag) {
  23953. var baseOptions = {
  23954. sources: [],
  23955. tracks: []
  23956. };
  23957. var tagOptions = getAttributes(tag);
  23958. var dataSetup = tagOptions['data-setup'];
  23959. if (hasClass(tag, 'vjs-fill')) {
  23960. tagOptions.fill = true;
  23961. }
  23962. if (hasClass(tag, 'vjs-fluid')) {
  23963. tagOptions.fluid = true;
  23964. } // Check if data-setup attr exists.
  23965. if (dataSetup !== null) {
  23966. // Parse options JSON
  23967. // If empty string, make it a parsable json object.
  23968. var _safeParseTuple = tuple(dataSetup || '{}'),
  23969. err = _safeParseTuple[0],
  23970. data = _safeParseTuple[1];
  23971. if (err) {
  23972. log.error(err);
  23973. }
  23974. assign(tagOptions, data);
  23975. }
  23976. assign(baseOptions, tagOptions); // Get tag children settings
  23977. if (tag.hasChildNodes()) {
  23978. var children = tag.childNodes;
  23979. for (var i = 0, j = children.length; i < j; i++) {
  23980. var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  23981. var childName = child.nodeName.toLowerCase();
  23982. if (childName === 'source') {
  23983. baseOptions.sources.push(getAttributes(child));
  23984. } else if (childName === 'track') {
  23985. baseOptions.tracks.push(getAttributes(child));
  23986. }
  23987. }
  23988. }
  23989. return baseOptions;
  23990. }
  23991. /**
  23992. * Determine whether or not flexbox is supported
  23993. *
  23994. * @return {boolean}
  23995. * - true if flexbox is supported
  23996. * - false if flexbox is not supported
  23997. */
  23998. ;
  23999. _proto.flexNotSupported_ = function flexNotSupported_() {
  24000. var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
  24001. // common flex features that we can rely on when checking for flex support.
  24002. 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
  24003. 'msFlexOrder' in elem.style);
  24004. };
  24005. return Player;
  24006. }(Component);
  24007. /**
  24008. * Get the {@link VideoTrackList}
  24009. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
  24010. *
  24011. * @return {VideoTrackList}
  24012. * the current video track list
  24013. *
  24014. * @method Player.prototype.videoTracks
  24015. */
  24016. /**
  24017. * Get the {@link AudioTrackList}
  24018. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
  24019. *
  24020. * @return {AudioTrackList}
  24021. * the current audio track list
  24022. *
  24023. * @method Player.prototype.audioTracks
  24024. */
  24025. /**
  24026. * Get the {@link TextTrackList}
  24027. *
  24028. * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  24029. *
  24030. * @return {TextTrackList}
  24031. * the current text track list
  24032. *
  24033. * @method Player.prototype.textTracks
  24034. */
  24035. /**
  24036. * Get the remote {@link TextTrackList}
  24037. *
  24038. * @return {TextTrackList}
  24039. * The current remote text track list
  24040. *
  24041. * @method Player.prototype.remoteTextTracks
  24042. */
  24043. /**
  24044. * Get the remote {@link HtmlTrackElementList} tracks.
  24045. *
  24046. * @return {HtmlTrackElementList}
  24047. * The current remote text track element list
  24048. *
  24049. * @method Player.prototype.remoteTextTrackEls
  24050. */
  24051. ALL.names.forEach(function (name) {
  24052. var props = ALL[name];
  24053. Player.prototype[props.getterName] = function () {
  24054. if (this.tech_) {
  24055. return this.tech_[props.getterName]();
  24056. } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
  24057. // these will be passed to the tech during loading
  24058. this[props.privateName] = this[props.privateName] || new props.ListClass();
  24059. return this[props.privateName];
  24060. };
  24061. });
  24062. /**
  24063. * Global enumeration of players.
  24064. *
  24065. * The keys are the player IDs and the values are either the {@link Player}
  24066. * instance or `null` for disposed players.
  24067. *
  24068. * @type {Object}
  24069. */
  24070. Player.players = {};
  24071. var navigator = window$1.navigator;
  24072. /*
  24073. * Player instance options, surfaced using options
  24074. * options = Player.prototype.options_
  24075. * Make changes in options, not here.
  24076. *
  24077. * @type {Object}
  24078. * @private
  24079. */
  24080. Player.prototype.options_ = {
  24081. // Default order of fallback technology
  24082. techOrder: Tech.defaultTechOrder_,
  24083. html5: {},
  24084. flash: {},
  24085. // default inactivity timeout
  24086. inactivityTimeout: 2000,
  24087. // default playback rates
  24088. playbackRates: [],
  24089. // Add playback rate selection by adding rates
  24090. // 'playbackRates': [0.5, 1, 1.5, 2],
  24091. liveui: false,
  24092. // Included control sets
  24093. children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
  24094. language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
  24095. // locales and their language translations
  24096. languages: {},
  24097. // Default message to show when a video cannot be played.
  24098. notSupportedMessage: 'No compatible source was found for this media.',
  24099. fullscreen: {
  24100. options: {
  24101. navigationUI: 'hide'
  24102. }
  24103. },
  24104. breakpoints: {},
  24105. responsive: false
  24106. };
  24107. [
  24108. /**
  24109. * Returns whether or not the player is in the "ended" state.
  24110. *
  24111. * @return {Boolean} True if the player is in the ended state, false if not.
  24112. * @method Player#ended
  24113. */
  24114. 'ended',
  24115. /**
  24116. * Returns whether or not the player is in the "seeking" state.
  24117. *
  24118. * @return {Boolean} True if the player is in the seeking state, false if not.
  24119. * @method Player#seeking
  24120. */
  24121. 'seeking',
  24122. /**
  24123. * Returns the TimeRanges of the media that are currently available
  24124. * for seeking to.
  24125. *
  24126. * @return {TimeRanges} the seekable intervals of the media timeline
  24127. * @method Player#seekable
  24128. */
  24129. 'seekable',
  24130. /**
  24131. * Returns the current state of network activity for the element, from
  24132. * the codes in the list below.
  24133. * - NETWORK_EMPTY (numeric value 0)
  24134. * The element has not yet been initialised. All attributes are in
  24135. * their initial states.
  24136. * - NETWORK_IDLE (numeric value 1)
  24137. * The element's resource selection algorithm is active and has
  24138. * selected a resource, but it is not actually using the network at
  24139. * this time.
  24140. * - NETWORK_LOADING (numeric value 2)
  24141. * The user agent is actively trying to download data.
  24142. * - NETWORK_NO_SOURCE (numeric value 3)
  24143. * The element's resource selection algorithm is active, but it has
  24144. * not yet found a resource to use.
  24145. *
  24146. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
  24147. * @return {number} the current network activity state
  24148. * @method Player#networkState
  24149. */
  24150. 'networkState',
  24151. /**
  24152. * Returns a value that expresses the current state of the element
  24153. * with respect to rendering the current playback position, from the
  24154. * codes in the list below.
  24155. * - HAVE_NOTHING (numeric value 0)
  24156. * No information regarding the media resource is available.
  24157. * - HAVE_METADATA (numeric value 1)
  24158. * Enough of the resource has been obtained that the duration of the
  24159. * resource is available.
  24160. * - HAVE_CURRENT_DATA (numeric value 2)
  24161. * Data for the immediate current playback position is available.
  24162. * - HAVE_FUTURE_DATA (numeric value 3)
  24163. * Data for the immediate current playback position is available, as
  24164. * well as enough data for the user agent to advance the current
  24165. * playback position in the direction of playback.
  24166. * - HAVE_ENOUGH_DATA (numeric value 4)
  24167. * The user agent estimates that enough data is available for
  24168. * playback to proceed uninterrupted.
  24169. *
  24170. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
  24171. * @return {number} the current playback rendering state
  24172. * @method Player#readyState
  24173. */
  24174. 'readyState'].forEach(function (fn) {
  24175. Player.prototype[fn] = function () {
  24176. return this.techGet_(fn);
  24177. };
  24178. });
  24179. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  24180. Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () {
  24181. return this.trigger(event);
  24182. };
  24183. });
  24184. /**
  24185. * Fired when the player has initial duration and dimension information
  24186. *
  24187. * @event Player#loadedmetadata
  24188. * @type {EventTarget~Event}
  24189. */
  24190. /**
  24191. * Fired when the player has downloaded data at the current playback position
  24192. *
  24193. * @event Player#loadeddata
  24194. * @type {EventTarget~Event}
  24195. */
  24196. /**
  24197. * Fired when the current playback position has changed *
  24198. * During playback this is fired every 15-250 milliseconds, depending on the
  24199. * playback technology in use.
  24200. *
  24201. * @event Player#timeupdate
  24202. * @type {EventTarget~Event}
  24203. */
  24204. /**
  24205. * Fired when the volume changes
  24206. *
  24207. * @event Player#volumechange
  24208. * @type {EventTarget~Event}
  24209. */
  24210. /**
  24211. * Reports whether or not a player has a plugin available.
  24212. *
  24213. * This does not report whether or not the plugin has ever been initialized
  24214. * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
  24215. *
  24216. * @method Player#hasPlugin
  24217. * @param {string} name
  24218. * The name of a plugin.
  24219. *
  24220. * @return {boolean}
  24221. * Whether or not this player has the requested plugin available.
  24222. */
  24223. /**
  24224. * Reports whether or not a player is using a plugin by name.
  24225. *
  24226. * For basic plugins, this only reports whether the plugin has _ever_ been
  24227. * initialized on this player.
  24228. *
  24229. * @method Player#usingPlugin
  24230. * @param {string} name
  24231. * The name of a plugin.
  24232. *
  24233. * @return {boolean}
  24234. * Whether or not this player is using the requested plugin.
  24235. */
  24236. Component.registerComponent('Player', Player);
  24237. /**
  24238. * The base plugin name.
  24239. *
  24240. * @private
  24241. * @constant
  24242. * @type {string}
  24243. */
  24244. var BASE_PLUGIN_NAME = 'plugin';
  24245. /**
  24246. * The key on which a player's active plugins cache is stored.
  24247. *
  24248. * @private
  24249. * @constant
  24250. * @type {string}
  24251. */
  24252. var PLUGIN_CACHE_KEY = 'activePlugins_';
  24253. /**
  24254. * Stores registered plugins in a private space.
  24255. *
  24256. * @private
  24257. * @type {Object}
  24258. */
  24259. var pluginStorage = {};
  24260. /**
  24261. * Reports whether or not a plugin has been registered.
  24262. *
  24263. * @private
  24264. * @param {string} name
  24265. * The name of a plugin.
  24266. *
  24267. * @return {boolean}
  24268. * Whether or not the plugin has been registered.
  24269. */
  24270. var pluginExists = function pluginExists(name) {
  24271. return pluginStorage.hasOwnProperty(name);
  24272. };
  24273. /**
  24274. * Get a single registered plugin by name.
  24275. *
  24276. * @private
  24277. * @param {string} name
  24278. * The name of a plugin.
  24279. *
  24280. * @return {Function|undefined}
  24281. * The plugin (or undefined).
  24282. */
  24283. var getPlugin = function getPlugin(name) {
  24284. return pluginExists(name) ? pluginStorage[name] : undefined;
  24285. };
  24286. /**
  24287. * Marks a plugin as "active" on a player.
  24288. *
  24289. * Also, ensures that the player has an object for tracking active plugins.
  24290. *
  24291. * @private
  24292. * @param {Player} player
  24293. * A Video.js player instance.
  24294. *
  24295. * @param {string} name
  24296. * The name of a plugin.
  24297. */
  24298. var markPluginAsActive = function markPluginAsActive(player, name) {
  24299. player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
  24300. player[PLUGIN_CACHE_KEY][name] = true;
  24301. };
  24302. /**
  24303. * Triggers a pair of plugin setup events.
  24304. *
  24305. * @private
  24306. * @param {Player} player
  24307. * A Video.js player instance.
  24308. *
  24309. * @param {Plugin~PluginEventHash} hash
  24310. * A plugin event hash.
  24311. *
  24312. * @param {boolean} [before]
  24313. * If true, prefixes the event name with "before". In other words,
  24314. * use this to trigger "beforepluginsetup" instead of "pluginsetup".
  24315. */
  24316. var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
  24317. var eventName = (before ? 'before' : '') + 'pluginsetup';
  24318. player.trigger(eventName, hash);
  24319. player.trigger(eventName + ':' + hash.name, hash);
  24320. };
  24321. /**
  24322. * Takes a basic plugin function and returns a wrapper function which marks
  24323. * on the player that the plugin has been activated.
  24324. *
  24325. * @private
  24326. * @param {string} name
  24327. * The name of the plugin.
  24328. *
  24329. * @param {Function} plugin
  24330. * The basic plugin.
  24331. *
  24332. * @return {Function}
  24333. * A wrapper function for the given plugin.
  24334. */
  24335. var createBasicPlugin = function createBasicPlugin(name, plugin) {
  24336. var basicPluginWrapper = function basicPluginWrapper() {
  24337. // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
  24338. // regardless, but we want the hash to be consistent with the hash provided
  24339. // for advanced plugins.
  24340. //
  24341. // The only potentially counter-intuitive thing here is the `instance` in
  24342. // the "pluginsetup" event is the value returned by the `plugin` function.
  24343. triggerSetupEvent(this, {
  24344. name: name,
  24345. plugin: plugin,
  24346. instance: null
  24347. }, true);
  24348. var instance = plugin.apply(this, arguments);
  24349. markPluginAsActive(this, name);
  24350. triggerSetupEvent(this, {
  24351. name: name,
  24352. plugin: plugin,
  24353. instance: instance
  24354. });
  24355. return instance;
  24356. };
  24357. Object.keys(plugin).forEach(function (prop) {
  24358. basicPluginWrapper[prop] = plugin[prop];
  24359. });
  24360. return basicPluginWrapper;
  24361. };
  24362. /**
  24363. * Takes a plugin sub-class and returns a factory function for generating
  24364. * instances of it.
  24365. *
  24366. * This factory function will replace itself with an instance of the requested
  24367. * sub-class of Plugin.
  24368. *
  24369. * @private
  24370. * @param {string} name
  24371. * The name of the plugin.
  24372. *
  24373. * @param {Plugin} PluginSubClass
  24374. * The advanced plugin.
  24375. *
  24376. * @return {Function}
  24377. */
  24378. var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
  24379. // Add a `name` property to the plugin prototype so that each plugin can
  24380. // refer to itself by name.
  24381. PluginSubClass.prototype.name = name;
  24382. return function () {
  24383. triggerSetupEvent(this, {
  24384. name: name,
  24385. plugin: PluginSubClass,
  24386. instance: null
  24387. }, true);
  24388. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  24389. args[_key] = arguments[_key];
  24390. }
  24391. var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
  24392. this[name] = function () {
  24393. return instance;
  24394. };
  24395. triggerSetupEvent(this, instance.getEventHash());
  24396. return instance;
  24397. };
  24398. };
  24399. /**
  24400. * Parent class for all advanced plugins.
  24401. *
  24402. * @mixes module:evented~EventedMixin
  24403. * @mixes module:stateful~StatefulMixin
  24404. * @fires Player#beforepluginsetup
  24405. * @fires Player#beforepluginsetup:$name
  24406. * @fires Player#pluginsetup
  24407. * @fires Player#pluginsetup:$name
  24408. * @listens Player#dispose
  24409. * @throws {Error}
  24410. * If attempting to instantiate the base {@link Plugin} class
  24411. * directly instead of via a sub-class.
  24412. */
  24413. var Plugin =
  24414. /*#__PURE__*/
  24415. function () {
  24416. /**
  24417. * Creates an instance of this class.
  24418. *
  24419. * Sub-classes should call `super` to ensure plugins are properly initialized.
  24420. *
  24421. * @param {Player} player
  24422. * A Video.js player instance.
  24423. */
  24424. function Plugin(player) {
  24425. if (this.constructor === Plugin) {
  24426. throw new Error('Plugin must be sub-classed; not directly instantiated.');
  24427. }
  24428. this.player = player; // Make this object evented, but remove the added `trigger` method so we
  24429. // use the prototype version instead.
  24430. evented(this);
  24431. delete this.trigger;
  24432. stateful(this, this.constructor.defaultState);
  24433. markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
  24434. // it later easily.
  24435. this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin.
  24436. player.on('dispose', this.dispose);
  24437. }
  24438. /**
  24439. * Get the version of the plugin that was set on <pluginName>.VERSION
  24440. */
  24441. var _proto = Plugin.prototype;
  24442. _proto.version = function version() {
  24443. return this.constructor.VERSION;
  24444. }
  24445. /**
  24446. * Each event triggered by plugins includes a hash of additional data with
  24447. * conventional properties.
  24448. *
  24449. * This returns that object or mutates an existing hash.
  24450. *
  24451. * @param {Object} [hash={}]
  24452. * An object to be used as event an event hash.
  24453. *
  24454. * @return {Plugin~PluginEventHash}
  24455. * An event hash object with provided properties mixed-in.
  24456. */
  24457. ;
  24458. _proto.getEventHash = function getEventHash(hash) {
  24459. if (hash === void 0) {
  24460. hash = {};
  24461. }
  24462. hash.name = this.name;
  24463. hash.plugin = this.constructor;
  24464. hash.instance = this;
  24465. return hash;
  24466. }
  24467. /**
  24468. * Triggers an event on the plugin object and overrides
  24469. * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
  24470. *
  24471. * @param {string|Object} event
  24472. * An event type or an object with a type property.
  24473. *
  24474. * @param {Object} [hash={}]
  24475. * Additional data hash to merge with a
  24476. * {@link Plugin~PluginEventHash|PluginEventHash}.
  24477. *
  24478. * @return {boolean}
  24479. * Whether or not default was prevented.
  24480. */
  24481. ;
  24482. _proto.trigger = function trigger$1(event, hash) {
  24483. if (hash === void 0) {
  24484. hash = {};
  24485. }
  24486. return trigger(this.eventBusEl_, event, this.getEventHash(hash));
  24487. }
  24488. /**
  24489. * Handles "statechanged" events on the plugin. No-op by default, override by
  24490. * subclassing.
  24491. *
  24492. * @abstract
  24493. * @param {Event} e
  24494. * An event object provided by a "statechanged" event.
  24495. *
  24496. * @param {Object} e.changes
  24497. * An object describing changes that occurred with the "statechanged"
  24498. * event.
  24499. */
  24500. ;
  24501. _proto.handleStateChanged = function handleStateChanged(e) {}
  24502. /**
  24503. * Disposes a plugin.
  24504. *
  24505. * Subclasses can override this if they want, but for the sake of safety,
  24506. * it's probably best to subscribe the "dispose" event.
  24507. *
  24508. * @fires Plugin#dispose
  24509. */
  24510. ;
  24511. _proto.dispose = function dispose() {
  24512. var name = this.name,
  24513. player = this.player;
  24514. /**
  24515. * Signals that a advanced plugin is about to be disposed.
  24516. *
  24517. * @event Plugin#dispose
  24518. * @type {EventTarget~Event}
  24519. */
  24520. this.trigger('dispose');
  24521. this.off();
  24522. player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
  24523. // references between the player and the plugin instance and nulling out
  24524. // the plugin's state and replacing methods with a function that throws.
  24525. player[PLUGIN_CACHE_KEY][name] = false;
  24526. this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
  24527. // function, so that the plugin is ready to be set up again.
  24528. player[name] = createPluginFactory(name, pluginStorage[name]);
  24529. }
  24530. /**
  24531. * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
  24532. *
  24533. * @param {string|Function} plugin
  24534. * If a string, matches the name of a plugin. If a function, will be
  24535. * tested directly.
  24536. *
  24537. * @return {boolean}
  24538. * Whether or not a plugin is a basic plugin.
  24539. */
  24540. ;
  24541. Plugin.isBasic = function isBasic(plugin) {
  24542. var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
  24543. return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
  24544. }
  24545. /**
  24546. * Register a Video.js plugin.
  24547. *
  24548. * @param {string} name
  24549. * The name of the plugin to be registered. Must be a string and
  24550. * must not match an existing plugin or a method on the `Player`
  24551. * prototype.
  24552. *
  24553. * @param {Function} plugin
  24554. * A sub-class of `Plugin` or a function for basic plugins.
  24555. *
  24556. * @return {Function}
  24557. * For advanced plugins, a factory function for that plugin. For
  24558. * basic plugins, a wrapper function that initializes the plugin.
  24559. */
  24560. ;
  24561. Plugin.registerPlugin = function registerPlugin(name, plugin) {
  24562. if (typeof name !== 'string') {
  24563. throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
  24564. }
  24565. if (pluginExists(name)) {
  24566. log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
  24567. } else if (Player.prototype.hasOwnProperty(name)) {
  24568. throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
  24569. }
  24570. if (typeof plugin !== 'function') {
  24571. throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
  24572. }
  24573. pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
  24574. // the base Plugin class).
  24575. if (name !== BASE_PLUGIN_NAME) {
  24576. if (Plugin.isBasic(plugin)) {
  24577. Player.prototype[name] = createBasicPlugin(name, plugin);
  24578. } else {
  24579. Player.prototype[name] = createPluginFactory(name, plugin);
  24580. }
  24581. }
  24582. return plugin;
  24583. }
  24584. /**
  24585. * De-register a Video.js plugin.
  24586. *
  24587. * @param {string} name
  24588. * The name of the plugin to be de-registered. Must be a string that
  24589. * matches an existing plugin.
  24590. *
  24591. * @throws {Error}
  24592. * If an attempt is made to de-register the base plugin.
  24593. */
  24594. ;
  24595. Plugin.deregisterPlugin = function deregisterPlugin(name) {
  24596. if (name === BASE_PLUGIN_NAME) {
  24597. throw new Error('Cannot de-register base plugin.');
  24598. }
  24599. if (pluginExists(name)) {
  24600. delete pluginStorage[name];
  24601. delete Player.prototype[name];
  24602. }
  24603. }
  24604. /**
  24605. * Gets an object containing multiple Video.js plugins.
  24606. *
  24607. * @param {Array} [names]
  24608. * If provided, should be an array of plugin names. Defaults to _all_
  24609. * plugin names.
  24610. *
  24611. * @return {Object|undefined}
  24612. * An object containing plugin(s) associated with their name(s) or
  24613. * `undefined` if no matching plugins exist).
  24614. */
  24615. ;
  24616. Plugin.getPlugins = function getPlugins(names) {
  24617. if (names === void 0) {
  24618. names = Object.keys(pluginStorage);
  24619. }
  24620. var result;
  24621. names.forEach(function (name) {
  24622. var plugin = getPlugin(name);
  24623. if (plugin) {
  24624. result = result || {};
  24625. result[name] = plugin;
  24626. }
  24627. });
  24628. return result;
  24629. }
  24630. /**
  24631. * Gets a plugin's version, if available
  24632. *
  24633. * @param {string} name
  24634. * The name of a plugin.
  24635. *
  24636. * @return {string}
  24637. * The plugin's version or an empty string.
  24638. */
  24639. ;
  24640. Plugin.getPluginVersion = function getPluginVersion(name) {
  24641. var plugin = getPlugin(name);
  24642. return plugin && plugin.VERSION || '';
  24643. };
  24644. return Plugin;
  24645. }();
  24646. /**
  24647. * Gets a plugin by name if it exists.
  24648. *
  24649. * @static
  24650. * @method getPlugin
  24651. * @memberOf Plugin
  24652. * @param {string} name
  24653. * The name of a plugin.
  24654. *
  24655. * @returns {Function|undefined}
  24656. * The plugin (or `undefined`).
  24657. */
  24658. Plugin.getPlugin = getPlugin;
  24659. /**
  24660. * The name of the base plugin class as it is registered.
  24661. *
  24662. * @type {string}
  24663. */
  24664. Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
  24665. Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
  24666. /**
  24667. * Documented in player.js
  24668. *
  24669. * @ignore
  24670. */
  24671. Player.prototype.usingPlugin = function (name) {
  24672. return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
  24673. };
  24674. /**
  24675. * Documented in player.js
  24676. *
  24677. * @ignore
  24678. */
  24679. Player.prototype.hasPlugin = function (name) {
  24680. return !!pluginExists(name);
  24681. };
  24682. /**
  24683. * Signals that a plugin is about to be set up on a player.
  24684. *
  24685. * @event Player#beforepluginsetup
  24686. * @type {Plugin~PluginEventHash}
  24687. */
  24688. /**
  24689. * Signals that a plugin is about to be set up on a player - by name. The name
  24690. * is the name of the plugin.
  24691. *
  24692. * @event Player#beforepluginsetup:$name
  24693. * @type {Plugin~PluginEventHash}
  24694. */
  24695. /**
  24696. * Signals that a plugin has just been set up on a player.
  24697. *
  24698. * @event Player#pluginsetup
  24699. * @type {Plugin~PluginEventHash}
  24700. */
  24701. /**
  24702. * Signals that a plugin has just been set up on a player - by name. The name
  24703. * is the name of the plugin.
  24704. *
  24705. * @event Player#pluginsetup:$name
  24706. * @type {Plugin~PluginEventHash}
  24707. */
  24708. /**
  24709. * @typedef {Object} Plugin~PluginEventHash
  24710. *
  24711. * @property {string} instance
  24712. * For basic plugins, the return value of the plugin function. For
  24713. * advanced plugins, the plugin instance on which the event is fired.
  24714. *
  24715. * @property {string} name
  24716. * The name of the plugin.
  24717. *
  24718. * @property {string} plugin
  24719. * For basic plugins, the plugin function. For advanced plugins, the
  24720. * plugin class/constructor.
  24721. */
  24722. /**
  24723. * @file extend.js
  24724. * @module extend
  24725. */
  24726. /**
  24727. * A combination of node inherits and babel's inherits (after transpile).
  24728. * Both work the same but node adds `super_` to the subClass
  24729. * and Bable adds the superClass as __proto__. Both seem useful.
  24730. *
  24731. * @param {Object} subClass
  24732. * The class to inherit to
  24733. *
  24734. * @param {Object} superClass
  24735. * The class to inherit from
  24736. *
  24737. * @private
  24738. */
  24739. var _inherits = function _inherits(subClass, superClass) {
  24740. if (typeof superClass !== 'function' && superClass !== null) {
  24741. throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  24742. }
  24743. subClass.prototype = Object.create(superClass && superClass.prototype, {
  24744. constructor: {
  24745. value: subClass,
  24746. enumerable: false,
  24747. writable: true,
  24748. configurable: true
  24749. }
  24750. });
  24751. if (superClass) {
  24752. // node
  24753. subClass.super_ = superClass;
  24754. }
  24755. };
  24756. /**
  24757. * Used to subclass an existing class by emulating ES subclassing using the
  24758. * `extends` keyword.
  24759. *
  24760. * @function
  24761. * @example
  24762. * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
  24763. * myCustomMethod: function() {
  24764. * // Do things in my method.
  24765. * }
  24766. * });
  24767. *
  24768. * @param {Function} superClass
  24769. * The class to inherit from
  24770. *
  24771. * @param {Object} [subClassMethods={}]
  24772. * Methods of the new class
  24773. *
  24774. * @return {Function}
  24775. * The new class with subClassMethods that inherited superClass.
  24776. */
  24777. var extend$1 = function extend(superClass, subClassMethods) {
  24778. if (subClassMethods === void 0) {
  24779. subClassMethods = {};
  24780. }
  24781. var subClass = function subClass() {
  24782. superClass.apply(this, arguments);
  24783. };
  24784. var methods = {};
  24785. if (typeof subClassMethods === 'object') {
  24786. if (subClassMethods.constructor !== Object.prototype.constructor) {
  24787. subClass = subClassMethods.constructor;
  24788. }
  24789. methods = subClassMethods;
  24790. } else if (typeof subClassMethods === 'function') {
  24791. subClass = subClassMethods;
  24792. }
  24793. _inherits(subClass, superClass); // Extend subObj's prototype with functions and other properties from props
  24794. for (var name in methods) {
  24795. if (methods.hasOwnProperty(name)) {
  24796. subClass.prototype[name] = methods[name];
  24797. }
  24798. }
  24799. return subClass;
  24800. };
  24801. /**
  24802. * @file video.js
  24803. * @module videojs
  24804. */
  24805. /**
  24806. * Normalize an `id` value by trimming off a leading `#`
  24807. *
  24808. * @private
  24809. * @param {string} id
  24810. * A string, maybe with a leading `#`.
  24811. *
  24812. * @return {string}
  24813. * The string, without any leading `#`.
  24814. */
  24815. var normalizeId = function normalizeId(id) {
  24816. return id.indexOf('#') === 0 ? id.slice(1) : id;
  24817. };
  24818. /**
  24819. * The `videojs()` function doubles as the main function for users to create a
  24820. * {@link Player} instance as well as the main library namespace.
  24821. *
  24822. * It can also be used as a getter for a pre-existing {@link Player} instance.
  24823. * However, we _strongly_ recommend using `videojs.getPlayer()` for this
  24824. * purpose because it avoids any potential for unintended initialization.
  24825. *
  24826. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  24827. * of our JSDoc template, we cannot properly document this as both a function
  24828. * and a namespace, so its function signature is documented here.
  24829. *
  24830. * #### Arguments
  24831. * ##### id
  24832. * string|Element, **required**
  24833. *
  24834. * Video element or video element ID.
  24835. *
  24836. * ##### options
  24837. * Object, optional
  24838. *
  24839. * Options object for providing settings.
  24840. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  24841. *
  24842. * ##### ready
  24843. * {@link Component~ReadyCallback}, optional
  24844. *
  24845. * A function to be called when the {@link Player} and {@link Tech} are ready.
  24846. *
  24847. * #### Return Value
  24848. *
  24849. * The `videojs()` function returns a {@link Player} instance.
  24850. *
  24851. * @namespace
  24852. *
  24853. * @borrows AudioTrack as AudioTrack
  24854. * @borrows Component.getComponent as getComponent
  24855. * @borrows module:computed-style~computedStyle as computedStyle
  24856. * @borrows module:events.on as on
  24857. * @borrows module:events.one as one
  24858. * @borrows module:events.off as off
  24859. * @borrows module:events.trigger as trigger
  24860. * @borrows EventTarget as EventTarget
  24861. * @borrows module:extend~extend as extend
  24862. * @borrows module:fn.bind as bind
  24863. * @borrows module:format-time.formatTime as formatTime
  24864. * @borrows module:format-time.resetFormatTime as resetFormatTime
  24865. * @borrows module:format-time.setFormatTime as setFormatTime
  24866. * @borrows module:merge-options.mergeOptions as mergeOptions
  24867. * @borrows module:middleware.use as use
  24868. * @borrows Player.players as players
  24869. * @borrows Plugin.registerPlugin as registerPlugin
  24870. * @borrows Plugin.deregisterPlugin as deregisterPlugin
  24871. * @borrows Plugin.getPlugins as getPlugins
  24872. * @borrows Plugin.getPlugin as getPlugin
  24873. * @borrows Plugin.getPluginVersion as getPluginVersion
  24874. * @borrows Tech.getTech as getTech
  24875. * @borrows Tech.registerTech as registerTech
  24876. * @borrows TextTrack as TextTrack
  24877. * @borrows module:time-ranges.createTimeRanges as createTimeRange
  24878. * @borrows module:time-ranges.createTimeRanges as createTimeRanges
  24879. * @borrows module:url.isCrossOrigin as isCrossOrigin
  24880. * @borrows module:url.parseUrl as parseUrl
  24881. * @borrows VideoTrack as VideoTrack
  24882. *
  24883. * @param {string|Element} id
  24884. * Video element or video element ID.
  24885. *
  24886. * @param {Object} [options]
  24887. * Options object for providing settings.
  24888. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  24889. *
  24890. * @param {Component~ReadyCallback} [ready]
  24891. * A function to be called when the {@link Player} and {@link Tech} are
  24892. * ready.
  24893. *
  24894. * @return {Player}
  24895. * The `videojs()` function returns a {@link Player|Player} instance.
  24896. */
  24897. function videojs$1(id, options, ready) {
  24898. var player = videojs$1.getPlayer(id);
  24899. if (player) {
  24900. if (options) {
  24901. log.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
  24902. }
  24903. if (ready) {
  24904. player.ready(ready);
  24905. }
  24906. return player;
  24907. }
  24908. var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
  24909. if (!isEl(el)) {
  24910. throw new TypeError('The element or ID supplied is not valid. (videojs)');
  24911. } // document.body.contains(el) will only check if el is contained within that one document.
  24912. // This causes problems for elements in iframes.
  24913. // Instead, use the element's ownerDocument instead of the global document.
  24914. // This will make sure that the element is indeed in the dom of that document.
  24915. // Additionally, check that the document in question has a default view.
  24916. // If the document is no longer attached to the dom, the defaultView of the document will be null.
  24917. if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
  24918. log.warn('The element supplied is not included in the DOM');
  24919. }
  24920. options = options || {};
  24921. videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
  24922. var opts = hookFunction(el, mergeOptions(options));
  24923. if (!isObject(opts) || Array.isArray(opts)) {
  24924. log.error('please return an object in beforesetup hooks');
  24925. return;
  24926. }
  24927. options = mergeOptions(options, opts);
  24928. }); // We get the current "Player" component here in case an integration has
  24929. // replaced it with a custom player.
  24930. var PlayerComponent = Component.getComponent('Player');
  24931. player = new PlayerComponent(el, options, ready);
  24932. videojs$1.hooks('setup').forEach(function (hookFunction) {
  24933. return hookFunction(player);
  24934. });
  24935. return player;
  24936. }
  24937. /**
  24938. * An Object that contains lifecycle hooks as keys which point to an array
  24939. * of functions that are run when a lifecycle is triggered
  24940. *
  24941. * @private
  24942. */
  24943. videojs$1.hooks_ = {};
  24944. /**
  24945. * Get a list of hooks for a specific lifecycle
  24946. *
  24947. * @param {string} type
  24948. * the lifecyle to get hooks from
  24949. *
  24950. * @param {Function|Function[]} [fn]
  24951. * Optionally add a hook (or hooks) to the lifecycle that your are getting.
  24952. *
  24953. * @return {Array}
  24954. * an array of hooks, or an empty array if there are none.
  24955. */
  24956. videojs$1.hooks = function (type, fn) {
  24957. videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
  24958. if (fn) {
  24959. videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
  24960. }
  24961. return videojs$1.hooks_[type];
  24962. };
  24963. /**
  24964. * Add a function hook to a specific videojs lifecycle.
  24965. *
  24966. * @param {string} type
  24967. * the lifecycle to hook the function to.
  24968. *
  24969. * @param {Function|Function[]}
  24970. * The function or array of functions to attach.
  24971. */
  24972. videojs$1.hook = function (type, fn) {
  24973. videojs$1.hooks(type, fn);
  24974. };
  24975. /**
  24976. * Add a function hook that will only run once to a specific videojs lifecycle.
  24977. *
  24978. * @param {string} type
  24979. * the lifecycle to hook the function to.
  24980. *
  24981. * @param {Function|Function[]}
  24982. * The function or array of functions to attach.
  24983. */
  24984. videojs$1.hookOnce = function (type, fn) {
  24985. videojs$1.hooks(type, [].concat(fn).map(function (original) {
  24986. var wrapper = function wrapper() {
  24987. videojs$1.removeHook(type, wrapper);
  24988. return original.apply(void 0, arguments);
  24989. };
  24990. return wrapper;
  24991. }));
  24992. };
  24993. /**
  24994. * Remove a hook from a specific videojs lifecycle.
  24995. *
  24996. * @param {string} type
  24997. * the lifecycle that the function hooked to
  24998. *
  24999. * @param {Function} fn
  25000. * The hooked function to remove
  25001. *
  25002. * @return {boolean}
  25003. * The function that was removed or undef
  25004. */
  25005. videojs$1.removeHook = function (type, fn) {
  25006. var index = videojs$1.hooks(type).indexOf(fn);
  25007. if (index <= -1) {
  25008. return false;
  25009. }
  25010. videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
  25011. videojs$1.hooks_[type].splice(index, 1);
  25012. return true;
  25013. }; // Add default styles
  25014. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
  25015. var style = $('.vjs-styles-defaults');
  25016. if (!style) {
  25017. style = createStyleElement('vjs-styles-defaults');
  25018. var head = $('head');
  25019. if (head) {
  25020. head.insertBefore(style, head.firstChild);
  25021. }
  25022. setTextContent(style, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ");
  25023. }
  25024. } // Run Auto-load players
  25025. // You have to wait at least once in case this script is loaded after your
  25026. // video in the DOM (weird behavior only with minified version)
  25027. autoSetupTimeout(1, videojs$1);
  25028. /**
  25029. * Current Video.js version. Follows [semantic versioning](https://semver.org/).
  25030. *
  25031. * @type {string}
  25032. */
  25033. videojs$1.VERSION = version;
  25034. /**
  25035. * The global options object. These are the settings that take effect
  25036. * if no overrides are specified when the player is created.
  25037. *
  25038. * @type {Object}
  25039. */
  25040. videojs$1.options = Player.prototype.options_;
  25041. /**
  25042. * Get an object with the currently created players, keyed by player ID
  25043. *
  25044. * @return {Object}
  25045. * The created players
  25046. */
  25047. videojs$1.getPlayers = function () {
  25048. return Player.players;
  25049. };
  25050. /**
  25051. * Get a single player based on an ID or DOM element.
  25052. *
  25053. * This is useful if you want to check if an element or ID has an associated
  25054. * Video.js player, but not create one if it doesn't.
  25055. *
  25056. * @param {string|Element} id
  25057. * An HTML element - `<video>`, `<audio>`, or `<video-js>` -
  25058. * or a string matching the `id` of such an element.
  25059. *
  25060. * @return {Player|undefined}
  25061. * A player instance or `undefined` if there is no player instance
  25062. * matching the argument.
  25063. */
  25064. videojs$1.getPlayer = function (id) {
  25065. var players = Player.players;
  25066. var tag;
  25067. if (typeof id === 'string') {
  25068. var nId = normalizeId(id);
  25069. var player = players[nId];
  25070. if (player) {
  25071. return player;
  25072. }
  25073. tag = $('#' + nId);
  25074. } else {
  25075. tag = id;
  25076. }
  25077. if (isEl(tag)) {
  25078. var _tag = tag,
  25079. _player = _tag.player,
  25080. playerId = _tag.playerId; // Element may have a `player` property referring to an already created
  25081. // player instance. If so, return that.
  25082. if (_player || players[playerId]) {
  25083. return _player || players[playerId];
  25084. }
  25085. }
  25086. };
  25087. /**
  25088. * Returns an array of all current players.
  25089. *
  25090. * @return {Array}
  25091. * An array of all players. The array will be in the order that
  25092. * `Object.keys` provides, which could potentially vary between
  25093. * JavaScript engines.
  25094. *
  25095. */
  25096. videojs$1.getAllPlayers = function () {
  25097. return (// Disposed players leave a key with a `null` value, so we need to make sure
  25098. // we filter those out.
  25099. Object.keys(Player.players).map(function (k) {
  25100. return Player.players[k];
  25101. }).filter(Boolean)
  25102. );
  25103. };
  25104. videojs$1.players = Player.players;
  25105. videojs$1.getComponent = Component.getComponent;
  25106. /**
  25107. * Register a component so it can referred to by name. Used when adding to other
  25108. * components, either through addChild `component.addChild('myComponent')` or through
  25109. * default children options `{ children: ['myComponent'] }`.
  25110. *
  25111. * > NOTE: You could also just initialize the component before adding.
  25112. * `component.addChild(new MyComponent());`
  25113. *
  25114. * @param {string} name
  25115. * The class name of the component
  25116. *
  25117. * @param {Component} comp
  25118. * The component class
  25119. *
  25120. * @return {Component}
  25121. * The newly registered component
  25122. */
  25123. videojs$1.registerComponent = function (name, comp) {
  25124. if (Tech.isTech(comp)) {
  25125. log.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
  25126. }
  25127. Component.registerComponent.call(Component, name, comp);
  25128. };
  25129. videojs$1.getTech = Tech.getTech;
  25130. videojs$1.registerTech = Tech.registerTech;
  25131. videojs$1.use = use;
  25132. /**
  25133. * An object that can be returned by a middleware to signify
  25134. * that the middleware is being terminated.
  25135. *
  25136. * @type {object}
  25137. * @property {object} middleware.TERMINATOR
  25138. */
  25139. Object.defineProperty(videojs$1, 'middleware', {
  25140. value: {},
  25141. writeable: false,
  25142. enumerable: true
  25143. });
  25144. Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
  25145. value: TERMINATOR,
  25146. writeable: false,
  25147. enumerable: true
  25148. });
  25149. /**
  25150. * A reference to the {@link module:browser|browser utility module} as an object.
  25151. *
  25152. * @type {Object}
  25153. * @see {@link module:browser|browser}
  25154. */
  25155. videojs$1.browser = browser;
  25156. /**
  25157. * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
  25158. * included for backward-compatibility with 4.x.
  25159. *
  25160. * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
  25161. * @type {boolean}
  25162. */
  25163. videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
  25164. videojs$1.extend = extend$1;
  25165. videojs$1.mergeOptions = mergeOptions;
  25166. videojs$1.bind = bind;
  25167. videojs$1.registerPlugin = Plugin.registerPlugin;
  25168. videojs$1.deregisterPlugin = Plugin.deregisterPlugin;
  25169. /**
  25170. * Deprecated method to register a plugin with Video.js
  25171. *
  25172. * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
  25173. *
  25174. * @param {string} name
  25175. * The plugin name
  25176. *
  25177. * @param {Plugin|Function} plugin
  25178. * The plugin sub-class or function
  25179. */
  25180. videojs$1.plugin = function (name, plugin) {
  25181. log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
  25182. return Plugin.registerPlugin(name, plugin);
  25183. };
  25184. videojs$1.getPlugins = Plugin.getPlugins;
  25185. videojs$1.getPlugin = Plugin.getPlugin;
  25186. videojs$1.getPluginVersion = Plugin.getPluginVersion;
  25187. /**
  25188. * Adding languages so that they're available to all players.
  25189. * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
  25190. *
  25191. * @param {string} code
  25192. * The language code or dictionary property
  25193. *
  25194. * @param {Object} data
  25195. * The data values to be translated
  25196. *
  25197. * @return {Object}
  25198. * The resulting language dictionary object
  25199. */
  25200. videojs$1.addLanguage = function (code, data) {
  25201. var _mergeOptions;
  25202. code = ('' + code).toLowerCase();
  25203. videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
  25204. return videojs$1.options.languages[code];
  25205. };
  25206. /**
  25207. * A reference to the {@link module:log|log utility module} as an object.
  25208. *
  25209. * @type {Function}
  25210. * @see {@link module:log|log}
  25211. */
  25212. videojs$1.log = log;
  25213. videojs$1.createLogger = createLogger$1;
  25214. videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
  25215. videojs$1.formatTime = formatTime;
  25216. videojs$1.setFormatTime = setFormatTime;
  25217. videojs$1.resetFormatTime = resetFormatTime;
  25218. videojs$1.parseUrl = parseUrl;
  25219. videojs$1.isCrossOrigin = isCrossOrigin;
  25220. videojs$1.EventTarget = EventTarget;
  25221. videojs$1.on = on;
  25222. videojs$1.one = one;
  25223. videojs$1.off = off;
  25224. videojs$1.trigger = trigger;
  25225. /**
  25226. * A cross-browser XMLHttpRequest wrapper.
  25227. *
  25228. * @function
  25229. * @param {Object} options
  25230. * Settings for the request.
  25231. *
  25232. * @return {XMLHttpRequest|XDomainRequest}
  25233. * The request object.
  25234. *
  25235. * @see https://github.com/Raynos/xhr
  25236. */
  25237. videojs$1.xhr = xhr;
  25238. videojs$1.TextTrack = TextTrack;
  25239. videojs$1.AudioTrack = AudioTrack;
  25240. videojs$1.VideoTrack = VideoTrack;
  25241. ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
  25242. videojs$1[k] = function () {
  25243. log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
  25244. return Dom[k].apply(null, arguments);
  25245. };
  25246. });
  25247. videojs$1.computedStyle = computedStyle;
  25248. /**
  25249. * A reference to the {@link module:dom|DOM utility module} as an object.
  25250. *
  25251. * @type {Object}
  25252. * @see {@link module:dom|dom}
  25253. */
  25254. videojs$1.dom = Dom;
  25255. /**
  25256. * A reference to the {@link module:url|URL utility module} as an object.
  25257. *
  25258. * @type {Object}
  25259. * @see {@link module:url|url}
  25260. */
  25261. videojs$1.url = Url;
  25262. var urlToolkit = createCommonjsModule(function (module, exports) {
  25263. // see https://tools.ietf.org/html/rfc1808
  25264. /* jshint ignore:start */
  25265. (function (root) {
  25266. /* jshint ignore:end */
  25267. var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
  25268. var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
  25269. var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
  25270. var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
  25271. var URLToolkit = {
  25272. // jshint ignore:line
  25273. // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
  25274. // E.g
  25275. // With opts.alwaysNormalize = false (default, spec compliant)
  25276. // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
  25277. // With opts.alwaysNormalize = true (not spec compliant)
  25278. // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
  25279. buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
  25280. opts = opts || {}; // remove any remaining space and CRLF
  25281. baseURL = baseURL.trim();
  25282. relativeURL = relativeURL.trim();
  25283. if (!relativeURL) {
  25284. // 2a) If the embedded URL is entirely empty, it inherits the
  25285. // entire base URL (i.e., is set equal to the base URL)
  25286. // and we are done.
  25287. if (!opts.alwaysNormalize) {
  25288. return baseURL;
  25289. }
  25290. var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  25291. if (!basePartsForNormalise) {
  25292. throw new Error('Error trying to parse base URL.');
  25293. }
  25294. basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
  25295. return URLToolkit.buildURLFromParts(basePartsForNormalise);
  25296. }
  25297. var relativeParts = URLToolkit.parseURL(relativeURL);
  25298. if (!relativeParts) {
  25299. throw new Error('Error trying to parse relative URL.');
  25300. }
  25301. if (relativeParts.scheme) {
  25302. // 2b) If the embedded URL starts with a scheme name, it is
  25303. // interpreted as an absolute URL and we are done.
  25304. if (!opts.alwaysNormalize) {
  25305. return relativeURL;
  25306. }
  25307. relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  25308. return URLToolkit.buildURLFromParts(relativeParts);
  25309. }
  25310. var baseParts = URLToolkit.parseURL(baseURL);
  25311. if (!baseParts) {
  25312. throw new Error('Error trying to parse base URL.');
  25313. }
  25314. if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  25315. // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
  25316. // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
  25317. var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  25318. baseParts.netLoc = pathParts[1];
  25319. baseParts.path = pathParts[2];
  25320. }
  25321. if (baseParts.netLoc && !baseParts.path) {
  25322. baseParts.path = '/';
  25323. }
  25324. var builtParts = {
  25325. // 2c) Otherwise, the embedded URL inherits the scheme of
  25326. // the base URL.
  25327. scheme: baseParts.scheme,
  25328. netLoc: relativeParts.netLoc,
  25329. path: null,
  25330. params: relativeParts.params,
  25331. query: relativeParts.query,
  25332. fragment: relativeParts.fragment
  25333. };
  25334. if (!relativeParts.netLoc) {
  25335. // 3) If the embedded URL's <net_loc> is non-empty, we skip to
  25336. // Step 7. Otherwise, the embedded URL inherits the <net_loc>
  25337. // (if any) of the base URL.
  25338. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
  25339. // path is not relative and we skip to Step 7.
  25340. if (relativeParts.path[0] !== '/') {
  25341. if (!relativeParts.path) {
  25342. // 5) If the embedded URL path is empty (and not preceded by a
  25343. // slash), then the embedded URL inherits the base URL path
  25344. builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
  25345. // step 7; otherwise, it inherits the <params> of the base
  25346. // URL (if any) and
  25347. if (!relativeParts.params) {
  25348. builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
  25349. // step 7; otherwise, it inherits the <query> of the base
  25350. // URL (if any) and we skip to step 7.
  25351. if (!relativeParts.query) {
  25352. builtParts.query = baseParts.query;
  25353. }
  25354. }
  25355. } else {
  25356. // 6) The last segment of the base URL's path (anything
  25357. // following the rightmost slash "/", or the entire path if no
  25358. // slash is present) is removed and the embedded URL's path is
  25359. // appended in its place.
  25360. var baseURLPath = baseParts.path;
  25361. var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
  25362. builtParts.path = URLToolkit.normalizePath(newPath);
  25363. }
  25364. }
  25365. }
  25366. if (builtParts.path === null) {
  25367. builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
  25368. }
  25369. return URLToolkit.buildURLFromParts(builtParts);
  25370. },
  25371. parseURL: function parseURL(url) {
  25372. var parts = URL_REGEX.exec(url);
  25373. if (!parts) {
  25374. return null;
  25375. }
  25376. return {
  25377. scheme: parts[1] || '',
  25378. netLoc: parts[2] || '',
  25379. path: parts[3] || '',
  25380. params: parts[4] || '',
  25381. query: parts[5] || '',
  25382. fragment: parts[6] || ''
  25383. };
  25384. },
  25385. normalizePath: function normalizePath(path) {
  25386. // The following operations are
  25387. // then applied, in order, to the new path:
  25388. // 6a) All occurrences of "./", where "." is a complete path
  25389. // segment, are removed.
  25390. // 6b) If the path ends with "." as a complete path segment,
  25391. // that "." is removed.
  25392. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
  25393. // complete path segment not equal to "..", are removed.
  25394. // Removal of these path segments is performed iteratively,
  25395. // removing the leftmost matching pattern on each iteration,
  25396. // until no matching pattern remains.
  25397. // 6d) If the path ends with "<segment>/..", where <segment> is a
  25398. // complete path segment not equal to "..", that
  25399. // "<segment>/.." is removed.
  25400. while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
  25401. return path.split('').reverse().join('');
  25402. },
  25403. buildURLFromParts: function buildURLFromParts(parts) {
  25404. return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
  25405. }
  25406. };
  25407. /* jshint ignore:start */
  25408. module.exports = URLToolkit;
  25409. })();
  25410. /* jshint ignore:end */
  25411. });
  25412. /*! @name m3u8-parser @version 4.3.0 @license Apache-2.0 */
  25413. function _extends() {
  25414. _extends = Object.assign || function (target) {
  25415. for (var i = 1; i < arguments.length; i++) {
  25416. var source = arguments[i];
  25417. for (var key in source) {
  25418. if (Object.prototype.hasOwnProperty.call(source, key)) {
  25419. target[key] = source[key];
  25420. }
  25421. }
  25422. }
  25423. return target;
  25424. };
  25425. return _extends.apply(this, arguments);
  25426. }
  25427. function _inheritsLoose$1(subClass, superClass) {
  25428. subClass.prototype = Object.create(superClass.prototype);
  25429. subClass.prototype.constructor = subClass;
  25430. subClass.__proto__ = superClass;
  25431. }
  25432. function _assertThisInitialized$1(self) {
  25433. if (self === void 0) {
  25434. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  25435. }
  25436. return self;
  25437. }
  25438. /**
  25439. * @file stream.js
  25440. */
  25441. /**
  25442. * A lightweight readable stream implementation that handles event dispatching.
  25443. *
  25444. * @class Stream
  25445. */
  25446. var Stream =
  25447. /*#__PURE__*/
  25448. function () {
  25449. function Stream() {
  25450. this.listeners = {};
  25451. }
  25452. /**
  25453. * Add a listener for a specified event type.
  25454. *
  25455. * @param {string} type the event name
  25456. * @param {Function} listener the callback to be invoked when an event of
  25457. * the specified type occurs
  25458. */
  25459. var _proto = Stream.prototype;
  25460. _proto.on = function on(type, listener) {
  25461. if (!this.listeners[type]) {
  25462. this.listeners[type] = [];
  25463. }
  25464. this.listeners[type].push(listener);
  25465. };
  25466. /**
  25467. * Remove a listener for a specified event type.
  25468. *
  25469. * @param {string} type the event name
  25470. * @param {Function} listener a function previously registered for this
  25471. * type of event through `on`
  25472. * @return {boolean} if we could turn it off or not
  25473. */
  25474. _proto.off = function off(type, listener) {
  25475. if (!this.listeners[type]) {
  25476. return false;
  25477. }
  25478. var index = this.listeners[type].indexOf(listener);
  25479. this.listeners[type].splice(index, 1);
  25480. return index > -1;
  25481. };
  25482. /**
  25483. * Trigger an event of the specified type on this stream. Any additional
  25484. * arguments to this function are passed as parameters to event listeners.
  25485. *
  25486. * @param {string} type the event name
  25487. */
  25488. _proto.trigger = function trigger(type) {
  25489. var callbacks = this.listeners[type];
  25490. var i;
  25491. var length;
  25492. var args;
  25493. if (!callbacks) {
  25494. return;
  25495. } // Slicing the arguments on every invocation of this method
  25496. // can add a significant amount of overhead. Avoid the
  25497. // intermediate object creation for the common case of a
  25498. // single callback argument
  25499. if (arguments.length === 2) {
  25500. length = callbacks.length;
  25501. for (i = 0; i < length; ++i) {
  25502. callbacks[i].call(this, arguments[1]);
  25503. }
  25504. } else {
  25505. args = Array.prototype.slice.call(arguments, 1);
  25506. length = callbacks.length;
  25507. for (i = 0; i < length; ++i) {
  25508. callbacks[i].apply(this, args);
  25509. }
  25510. }
  25511. };
  25512. /**
  25513. * Destroys the stream and cleans up.
  25514. */
  25515. _proto.dispose = function dispose() {
  25516. this.listeners = {};
  25517. };
  25518. /**
  25519. * Forwards all `data` events on this stream to the destination stream. The
  25520. * destination stream should provide a method `push` to receive the data
  25521. * events as they arrive.
  25522. *
  25523. * @param {Stream} destination the stream that will receive all `data` events
  25524. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  25525. */
  25526. _proto.pipe = function pipe(destination) {
  25527. this.on('data', function (data) {
  25528. destination.push(data);
  25529. });
  25530. };
  25531. return Stream;
  25532. }();
  25533. /**
  25534. * A stream that buffers string input and generates a `data` event for each
  25535. * line.
  25536. *
  25537. * @class LineStream
  25538. * @extends Stream
  25539. */
  25540. var LineStream =
  25541. /*#__PURE__*/
  25542. function (_Stream) {
  25543. _inheritsLoose$1(LineStream, _Stream);
  25544. function LineStream() {
  25545. var _this;
  25546. _this = _Stream.call(this) || this;
  25547. _this.buffer = '';
  25548. return _this;
  25549. }
  25550. /**
  25551. * Add new data to be parsed.
  25552. *
  25553. * @param {string} data the text to process
  25554. */
  25555. var _proto = LineStream.prototype;
  25556. _proto.push = function push(data) {
  25557. var nextNewline;
  25558. this.buffer += data;
  25559. nextNewline = this.buffer.indexOf('\n');
  25560. for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
  25561. this.trigger('data', this.buffer.substring(0, nextNewline));
  25562. this.buffer = this.buffer.substring(nextNewline + 1);
  25563. }
  25564. };
  25565. return LineStream;
  25566. }(Stream);
  25567. /**
  25568. * "forgiving" attribute list psuedo-grammar:
  25569. * attributes -> keyvalue (',' keyvalue)*
  25570. * keyvalue -> key '=' value
  25571. * key -> [^=]*
  25572. * value -> '"' [^"]* '"' | [^,]*
  25573. */
  25574. var attributeSeparator = function attributeSeparator() {
  25575. var key = '[^=]*';
  25576. var value = '"[^"]*"|[^,]*';
  25577. var keyvalue = '(?:' + key + ')=(?:' + value + ')';
  25578. return new RegExp('(?:^|,)(' + keyvalue + ')');
  25579. };
  25580. /**
  25581. * Parse attributes from a line given the separator
  25582. *
  25583. * @param {string} attributes the attribute line to parse
  25584. */
  25585. var parseAttributes = function parseAttributes(attributes) {
  25586. // split the string using attributes as the separator
  25587. var attrs = attributes.split(attributeSeparator());
  25588. var result = {};
  25589. var i = attrs.length;
  25590. var attr;
  25591. while (i--) {
  25592. // filter out unmatched portions of the string
  25593. if (attrs[i] === '') {
  25594. continue;
  25595. } // split the key and value
  25596. attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
  25597. attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
  25598. attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
  25599. attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
  25600. result[attr[0]] = attr[1];
  25601. }
  25602. return result;
  25603. };
  25604. /**
  25605. * A line-level M3U8 parser event stream. It expects to receive input one
  25606. * line at a time and performs a context-free parse of its contents. A stream
  25607. * interpretation of a manifest can be useful if the manifest is expected to
  25608. * be too large to fit comfortably into memory or the entirety of the input
  25609. * is not immediately available. Otherwise, it's probably much easier to work
  25610. * with a regular `Parser` object.
  25611. *
  25612. * Produces `data` events with an object that captures the parser's
  25613. * interpretation of the input. That object has a property `tag` that is one
  25614. * of `uri`, `comment`, or `tag`. URIs only have a single additional
  25615. * property, `line`, which captures the entirety of the input without
  25616. * interpretation. Comments similarly have a single additional property
  25617. * `text` which is the input without the leading `#`.
  25618. *
  25619. * Tags always have a property `tagType` which is the lower-cased version of
  25620. * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
  25621. * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
  25622. * tags are given the tag type `unknown` and a single additional property
  25623. * `data` with the remainder of the input.
  25624. *
  25625. * @class ParseStream
  25626. * @extends Stream
  25627. */
  25628. var ParseStream =
  25629. /*#__PURE__*/
  25630. function (_Stream) {
  25631. _inheritsLoose$1(ParseStream, _Stream);
  25632. function ParseStream() {
  25633. var _this;
  25634. _this = _Stream.call(this) || this;
  25635. _this.customParsers = [];
  25636. _this.tagMappers = [];
  25637. return _this;
  25638. }
  25639. /**
  25640. * Parses an additional line of input.
  25641. *
  25642. * @param {string} line a single line of an M3U8 file to parse
  25643. */
  25644. var _proto = ParseStream.prototype;
  25645. _proto.push = function push(line) {
  25646. var _this2 = this;
  25647. var match;
  25648. var event; // strip whitespace
  25649. line = line.trim();
  25650. if (line.length === 0) {
  25651. // ignore empty lines
  25652. return;
  25653. } // URIs
  25654. if (line[0] !== '#') {
  25655. this.trigger('data', {
  25656. type: 'uri',
  25657. uri: line
  25658. });
  25659. return;
  25660. } // map tags
  25661. var newLines = this.tagMappers.reduce(function (acc, mapper) {
  25662. var mappedLine = mapper(line); // skip if unchanged
  25663. if (mappedLine === line) {
  25664. return acc;
  25665. }
  25666. return acc.concat([mappedLine]);
  25667. }, [line]);
  25668. newLines.forEach(function (newLine) {
  25669. for (var i = 0; i < _this2.customParsers.length; i++) {
  25670. if (_this2.customParsers[i].call(_this2, newLine)) {
  25671. return;
  25672. }
  25673. } // Comments
  25674. if (newLine.indexOf('#EXT') !== 0) {
  25675. _this2.trigger('data', {
  25676. type: 'comment',
  25677. text: newLine.slice(1)
  25678. });
  25679. return;
  25680. } // strip off any carriage returns here so the regex matching
  25681. // doesn't have to account for them.
  25682. newLine = newLine.replace('\r', ''); // Tags
  25683. match = /^#EXTM3U/.exec(newLine);
  25684. if (match) {
  25685. _this2.trigger('data', {
  25686. type: 'tag',
  25687. tagType: 'm3u'
  25688. });
  25689. return;
  25690. }
  25691. match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
  25692. if (match) {
  25693. event = {
  25694. type: 'tag',
  25695. tagType: 'inf'
  25696. };
  25697. if (match[1]) {
  25698. event.duration = parseFloat(match[1]);
  25699. }
  25700. if (match[2]) {
  25701. event.title = match[2];
  25702. }
  25703. _this2.trigger('data', event);
  25704. return;
  25705. }
  25706. match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
  25707. if (match) {
  25708. event = {
  25709. type: 'tag',
  25710. tagType: 'targetduration'
  25711. };
  25712. if (match[1]) {
  25713. event.duration = parseInt(match[1], 10);
  25714. }
  25715. _this2.trigger('data', event);
  25716. return;
  25717. }
  25718. match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine);
  25719. if (match) {
  25720. event = {
  25721. type: 'tag',
  25722. tagType: 'totalduration'
  25723. };
  25724. if (match[1]) {
  25725. event.duration = parseInt(match[1], 10);
  25726. }
  25727. _this2.trigger('data', event);
  25728. return;
  25729. }
  25730. match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
  25731. if (match) {
  25732. event = {
  25733. type: 'tag',
  25734. tagType: 'version'
  25735. };
  25736. if (match[1]) {
  25737. event.version = parseInt(match[1], 10);
  25738. }
  25739. _this2.trigger('data', event);
  25740. return;
  25741. }
  25742. match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
  25743. if (match) {
  25744. event = {
  25745. type: 'tag',
  25746. tagType: 'media-sequence'
  25747. };
  25748. if (match[1]) {
  25749. event.number = parseInt(match[1], 10);
  25750. }
  25751. _this2.trigger('data', event);
  25752. return;
  25753. }
  25754. match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
  25755. if (match) {
  25756. event = {
  25757. type: 'tag',
  25758. tagType: 'discontinuity-sequence'
  25759. };
  25760. if (match[1]) {
  25761. event.number = parseInt(match[1], 10);
  25762. }
  25763. _this2.trigger('data', event);
  25764. return;
  25765. }
  25766. match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
  25767. if (match) {
  25768. event = {
  25769. type: 'tag',
  25770. tagType: 'playlist-type'
  25771. };
  25772. if (match[1]) {
  25773. event.playlistType = match[1];
  25774. }
  25775. _this2.trigger('data', event);
  25776. return;
  25777. }
  25778. match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine);
  25779. if (match) {
  25780. event = {
  25781. type: 'tag',
  25782. tagType: 'byterange'
  25783. };
  25784. if (match[1]) {
  25785. event.length = parseInt(match[1], 10);
  25786. }
  25787. if (match[2]) {
  25788. event.offset = parseInt(match[2], 10);
  25789. }
  25790. _this2.trigger('data', event);
  25791. return;
  25792. }
  25793. match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine);
  25794. if (match) {
  25795. event = {
  25796. type: 'tag',
  25797. tagType: 'allow-cache'
  25798. };
  25799. if (match[1]) {
  25800. event.allowed = !/NO/.test(match[1]);
  25801. }
  25802. _this2.trigger('data', event);
  25803. return;
  25804. }
  25805. match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
  25806. if (match) {
  25807. event = {
  25808. type: 'tag',
  25809. tagType: 'map'
  25810. };
  25811. if (match[1]) {
  25812. var attributes = parseAttributes(match[1]);
  25813. if (attributes.URI) {
  25814. event.uri = attributes.URI;
  25815. }
  25816. if (attributes.BYTERANGE) {
  25817. var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
  25818. length = _attributes$BYTERANGE[0],
  25819. offset = _attributes$BYTERANGE[1];
  25820. event.byterange = {};
  25821. if (length) {
  25822. event.byterange.length = parseInt(length, 10);
  25823. }
  25824. if (offset) {
  25825. event.byterange.offset = parseInt(offset, 10);
  25826. }
  25827. }
  25828. }
  25829. _this2.trigger('data', event);
  25830. return;
  25831. }
  25832. match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
  25833. if (match) {
  25834. event = {
  25835. type: 'tag',
  25836. tagType: 'stream-inf'
  25837. };
  25838. if (match[1]) {
  25839. event.attributes = parseAttributes(match[1]);
  25840. if (event.attributes.RESOLUTION) {
  25841. var split = event.attributes.RESOLUTION.split('x');
  25842. var resolution = {};
  25843. if (split[0]) {
  25844. resolution.width = parseInt(split[0], 10);
  25845. }
  25846. if (split[1]) {
  25847. resolution.height = parseInt(split[1], 10);
  25848. }
  25849. event.attributes.RESOLUTION = resolution;
  25850. }
  25851. if (event.attributes.BANDWIDTH) {
  25852. event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
  25853. }
  25854. if (event.attributes['PROGRAM-ID']) {
  25855. event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
  25856. }
  25857. }
  25858. _this2.trigger('data', event);
  25859. return;
  25860. }
  25861. match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
  25862. if (match) {
  25863. event = {
  25864. type: 'tag',
  25865. tagType: 'media'
  25866. };
  25867. if (match[1]) {
  25868. event.attributes = parseAttributes(match[1]);
  25869. }
  25870. _this2.trigger('data', event);
  25871. return;
  25872. }
  25873. match = /^#EXT-X-ENDLIST/.exec(newLine);
  25874. if (match) {
  25875. _this2.trigger('data', {
  25876. type: 'tag',
  25877. tagType: 'endlist'
  25878. });
  25879. return;
  25880. }
  25881. match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
  25882. if (match) {
  25883. _this2.trigger('data', {
  25884. type: 'tag',
  25885. tagType: 'discontinuity'
  25886. });
  25887. return;
  25888. }
  25889. match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine);
  25890. if (match) {
  25891. event = {
  25892. type: 'tag',
  25893. tagType: 'program-date-time'
  25894. };
  25895. if (match[1]) {
  25896. event.dateTimeString = match[1];
  25897. event.dateTimeObject = new Date(match[1]);
  25898. }
  25899. _this2.trigger('data', event);
  25900. return;
  25901. }
  25902. match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
  25903. if (match) {
  25904. event = {
  25905. type: 'tag',
  25906. tagType: 'key'
  25907. };
  25908. if (match[1]) {
  25909. event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
  25910. if (event.attributes.IV) {
  25911. if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
  25912. event.attributes.IV = event.attributes.IV.substring(2);
  25913. }
  25914. event.attributes.IV = event.attributes.IV.match(/.{8}/g);
  25915. event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
  25916. event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
  25917. event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
  25918. event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
  25919. event.attributes.IV = new Uint32Array(event.attributes.IV);
  25920. }
  25921. }
  25922. _this2.trigger('data', event);
  25923. return;
  25924. }
  25925. match = /^#EXT-X-START:?(.*)$/.exec(newLine);
  25926. if (match) {
  25927. event = {
  25928. type: 'tag',
  25929. tagType: 'start'
  25930. };
  25931. if (match[1]) {
  25932. event.attributes = parseAttributes(match[1]);
  25933. event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
  25934. event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
  25935. }
  25936. _this2.trigger('data', event);
  25937. return;
  25938. }
  25939. match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
  25940. if (match) {
  25941. event = {
  25942. type: 'tag',
  25943. tagType: 'cue-out-cont'
  25944. };
  25945. if (match[1]) {
  25946. event.data = match[1];
  25947. } else {
  25948. event.data = '';
  25949. }
  25950. _this2.trigger('data', event);
  25951. return;
  25952. }
  25953. match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
  25954. if (match) {
  25955. event = {
  25956. type: 'tag',
  25957. tagType: 'cue-out'
  25958. };
  25959. if (match[1]) {
  25960. event.data = match[1];
  25961. } else {
  25962. event.data = '';
  25963. }
  25964. _this2.trigger('data', event);
  25965. return;
  25966. }
  25967. match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
  25968. if (match) {
  25969. event = {
  25970. type: 'tag',
  25971. tagType: 'cue-in'
  25972. };
  25973. if (match[1]) {
  25974. event.data = match[1];
  25975. } else {
  25976. event.data = '';
  25977. }
  25978. _this2.trigger('data', event);
  25979. return;
  25980. } // unknown tag type
  25981. _this2.trigger('data', {
  25982. type: 'tag',
  25983. data: newLine.slice(4)
  25984. });
  25985. });
  25986. };
  25987. /**
  25988. * Add a parser for custom headers
  25989. *
  25990. * @param {Object} options a map of options for the added parser
  25991. * @param {RegExp} options.expression a regular expression to match the custom header
  25992. * @param {string} options.customType the custom type to register to the output
  25993. * @param {Function} [options.dataParser] function to parse the line into an object
  25994. * @param {boolean} [options.segment] should tag data be attached to the segment object
  25995. */
  25996. _proto.addParser = function addParser(_ref) {
  25997. var _this3 = this;
  25998. var expression = _ref.expression,
  25999. customType = _ref.customType,
  26000. dataParser = _ref.dataParser,
  26001. segment = _ref.segment;
  26002. if (typeof dataParser !== 'function') {
  26003. dataParser = function dataParser(line) {
  26004. return line;
  26005. };
  26006. }
  26007. this.customParsers.push(function (line) {
  26008. var match = expression.exec(line);
  26009. if (match) {
  26010. _this3.trigger('data', {
  26011. type: 'custom',
  26012. data: dataParser(line),
  26013. customType: customType,
  26014. segment: segment
  26015. });
  26016. return true;
  26017. }
  26018. });
  26019. };
  26020. /**
  26021. * Add a custom header mapper
  26022. *
  26023. * @param {Object} options
  26024. * @param {RegExp} options.expression a regular expression to match the custom header
  26025. * @param {Function} options.map function to translate tag into a different tag
  26026. */
  26027. _proto.addTagMapper = function addTagMapper(_ref2) {
  26028. var expression = _ref2.expression,
  26029. map = _ref2.map;
  26030. var mapFn = function mapFn(line) {
  26031. if (expression.test(line)) {
  26032. return map(line);
  26033. }
  26034. return line;
  26035. };
  26036. this.tagMappers.push(mapFn);
  26037. };
  26038. return ParseStream;
  26039. }(Stream);
  26040. /**
  26041. * A parser for M3U8 files. The current interpretation of the input is
  26042. * exposed as a property `manifest` on parser objects. It's just two lines to
  26043. * create and parse a manifest once you have the contents available as a string:
  26044. *
  26045. * ```js
  26046. * var parser = new m3u8.Parser();
  26047. * parser.push(xhr.responseText);
  26048. * ```
  26049. *
  26050. * New input can later be applied to update the manifest object by calling
  26051. * `push` again.
  26052. *
  26053. * The parser attempts to create a usable manifest object even if the
  26054. * underlying input is somewhat nonsensical. It emits `info` and `warning`
  26055. * events during the parse if it encounters input that seems invalid or
  26056. * requires some property of the manifest object to be defaulted.
  26057. *
  26058. * @class Parser
  26059. * @extends Stream
  26060. */
  26061. var Parser =
  26062. /*#__PURE__*/
  26063. function (_Stream) {
  26064. _inheritsLoose$1(Parser, _Stream);
  26065. function Parser() {
  26066. var _this;
  26067. _this = _Stream.call(this) || this;
  26068. _this.lineStream = new LineStream();
  26069. _this.parseStream = new ParseStream();
  26070. _this.lineStream.pipe(_this.parseStream);
  26071. /* eslint-disable consistent-this */
  26072. var self = _assertThisInitialized$1(_assertThisInitialized$1(_this));
  26073. /* eslint-enable consistent-this */
  26074. var uris = [];
  26075. var currentUri = {}; // if specified, the active EXT-X-MAP definition
  26076. var currentMap; // if specified, the active decryption key
  26077. var _key;
  26078. var noop = function noop() {};
  26079. var defaultMediaGroups = {
  26080. 'AUDIO': {},
  26081. 'VIDEO': {},
  26082. 'CLOSED-CAPTIONS': {},
  26083. 'SUBTITLES': {}
  26084. }; // group segments into numbered timelines delineated by discontinuities
  26085. var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
  26086. _this.manifest = {
  26087. allowCache: true,
  26088. discontinuityStarts: [],
  26089. segments: []
  26090. }; // update the manifest with the m3u8 entry from the parse stream
  26091. _this.parseStream.on('data', function (entry) {
  26092. var mediaGroup;
  26093. var rendition;
  26094. ({
  26095. tag: function tag() {
  26096. // switch based on the tag type
  26097. (({
  26098. 'allow-cache': function allowCache() {
  26099. this.manifest.allowCache = entry.allowed;
  26100. if (!('allowed' in entry)) {
  26101. this.trigger('info', {
  26102. message: 'defaulting allowCache to YES'
  26103. });
  26104. this.manifest.allowCache = true;
  26105. }
  26106. },
  26107. byterange: function byterange() {
  26108. var byterange = {};
  26109. if ('length' in entry) {
  26110. currentUri.byterange = byterange;
  26111. byterange.length = entry.length;
  26112. if (!('offset' in entry)) {
  26113. this.trigger('info', {
  26114. message: 'defaulting offset to zero'
  26115. });
  26116. entry.offset = 0;
  26117. }
  26118. }
  26119. if ('offset' in entry) {
  26120. currentUri.byterange = byterange;
  26121. byterange.offset = entry.offset;
  26122. }
  26123. },
  26124. endlist: function endlist() {
  26125. this.manifest.endList = true;
  26126. },
  26127. inf: function inf() {
  26128. if (!('mediaSequence' in this.manifest)) {
  26129. this.manifest.mediaSequence = 0;
  26130. this.trigger('info', {
  26131. message: 'defaulting media sequence to zero'
  26132. });
  26133. }
  26134. if (!('discontinuitySequence' in this.manifest)) {
  26135. this.manifest.discontinuitySequence = 0;
  26136. this.trigger('info', {
  26137. message: 'defaulting discontinuity sequence to zero'
  26138. });
  26139. }
  26140. if (entry.duration > 0) {
  26141. currentUri.duration = entry.duration;
  26142. }
  26143. if (entry.duration === 0) {
  26144. currentUri.duration = 0.01;
  26145. this.trigger('info', {
  26146. message: 'updating zero segment duration to a small value'
  26147. });
  26148. }
  26149. this.manifest.segments = uris;
  26150. },
  26151. key: function key() {
  26152. if (!entry.attributes) {
  26153. this.trigger('warn', {
  26154. message: 'ignoring key declaration without attribute list'
  26155. });
  26156. return;
  26157. } // clear the active encryption key
  26158. if (entry.attributes.METHOD === 'NONE') {
  26159. _key = null;
  26160. return;
  26161. }
  26162. if (!entry.attributes.URI) {
  26163. this.trigger('warn', {
  26164. message: 'ignoring key declaration without URI'
  26165. });
  26166. return;
  26167. }
  26168. if (!entry.attributes.METHOD) {
  26169. this.trigger('warn', {
  26170. message: 'defaulting key method to AES-128'
  26171. });
  26172. } // setup an encryption key for upcoming segments
  26173. _key = {
  26174. method: entry.attributes.METHOD || 'AES-128',
  26175. uri: entry.attributes.URI
  26176. };
  26177. if (typeof entry.attributes.IV !== 'undefined') {
  26178. _key.iv = entry.attributes.IV;
  26179. }
  26180. },
  26181. 'media-sequence': function mediaSequence() {
  26182. if (!isFinite(entry.number)) {
  26183. this.trigger('warn', {
  26184. message: 'ignoring invalid media sequence: ' + entry.number
  26185. });
  26186. return;
  26187. }
  26188. this.manifest.mediaSequence = entry.number;
  26189. },
  26190. 'discontinuity-sequence': function discontinuitySequence() {
  26191. if (!isFinite(entry.number)) {
  26192. this.trigger('warn', {
  26193. message: 'ignoring invalid discontinuity sequence: ' + entry.number
  26194. });
  26195. return;
  26196. }
  26197. this.manifest.discontinuitySequence = entry.number;
  26198. currentTimeline = entry.number;
  26199. },
  26200. 'playlist-type': function playlistType() {
  26201. if (!/VOD|EVENT/.test(entry.playlistType)) {
  26202. this.trigger('warn', {
  26203. message: 'ignoring unknown playlist type: ' + entry.playlist
  26204. });
  26205. return;
  26206. }
  26207. this.manifest.playlistType = entry.playlistType;
  26208. },
  26209. map: function map() {
  26210. currentMap = {};
  26211. if (entry.uri) {
  26212. currentMap.uri = entry.uri;
  26213. }
  26214. if (entry.byterange) {
  26215. currentMap.byterange = entry.byterange;
  26216. }
  26217. },
  26218. 'stream-inf': function streamInf() {
  26219. this.manifest.playlists = uris;
  26220. this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
  26221. if (!entry.attributes) {
  26222. this.trigger('warn', {
  26223. message: 'ignoring empty stream-inf attributes'
  26224. });
  26225. return;
  26226. }
  26227. if (!currentUri.attributes) {
  26228. currentUri.attributes = {};
  26229. }
  26230. _extends(currentUri.attributes, entry.attributes);
  26231. },
  26232. media: function media() {
  26233. this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
  26234. if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
  26235. this.trigger('warn', {
  26236. message: 'ignoring incomplete or missing media group'
  26237. });
  26238. return;
  26239. } // find the media group, creating defaults as necessary
  26240. var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
  26241. mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
  26242. mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
  26243. rendition = {
  26244. "default": /yes/i.test(entry.attributes.DEFAULT)
  26245. };
  26246. if (rendition["default"]) {
  26247. rendition.autoselect = true;
  26248. } else {
  26249. rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
  26250. }
  26251. if (entry.attributes.LANGUAGE) {
  26252. rendition.language = entry.attributes.LANGUAGE;
  26253. }
  26254. if (entry.attributes.URI) {
  26255. rendition.uri = entry.attributes.URI;
  26256. }
  26257. if (entry.attributes['INSTREAM-ID']) {
  26258. rendition.instreamId = entry.attributes['INSTREAM-ID'];
  26259. }
  26260. if (entry.attributes.CHARACTERISTICS) {
  26261. rendition.characteristics = entry.attributes.CHARACTERISTICS;
  26262. }
  26263. if (entry.attributes.FORCED) {
  26264. rendition.forced = /yes/i.test(entry.attributes.FORCED);
  26265. } // insert the new rendition
  26266. mediaGroup[entry.attributes.NAME] = rendition;
  26267. },
  26268. discontinuity: function discontinuity() {
  26269. currentTimeline += 1;
  26270. currentUri.discontinuity = true;
  26271. this.manifest.discontinuityStarts.push(uris.length);
  26272. },
  26273. 'program-date-time': function programDateTime() {
  26274. if (typeof this.manifest.dateTimeString === 'undefined') {
  26275. // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
  26276. // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
  26277. // to the manifest object
  26278. // TODO: Consider removing this in future major version
  26279. this.manifest.dateTimeString = entry.dateTimeString;
  26280. this.manifest.dateTimeObject = entry.dateTimeObject;
  26281. }
  26282. currentUri.dateTimeString = entry.dateTimeString;
  26283. currentUri.dateTimeObject = entry.dateTimeObject;
  26284. },
  26285. targetduration: function targetduration() {
  26286. if (!isFinite(entry.duration) || entry.duration < 0) {
  26287. this.trigger('warn', {
  26288. message: 'ignoring invalid target duration: ' + entry.duration
  26289. });
  26290. return;
  26291. }
  26292. this.manifest.targetDuration = entry.duration;
  26293. },
  26294. totalduration: function totalduration() {
  26295. if (!isFinite(entry.duration) || entry.duration < 0) {
  26296. this.trigger('warn', {
  26297. message: 'ignoring invalid total duration: ' + entry.duration
  26298. });
  26299. return;
  26300. }
  26301. this.manifest.totalDuration = entry.duration;
  26302. },
  26303. start: function start() {
  26304. if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
  26305. this.trigger('warn', {
  26306. message: 'ignoring start declaration without appropriate attribute list'
  26307. });
  26308. return;
  26309. }
  26310. this.manifest.start = {
  26311. timeOffset: entry.attributes['TIME-OFFSET'],
  26312. precise: entry.attributes.PRECISE
  26313. };
  26314. },
  26315. 'cue-out': function cueOut() {
  26316. currentUri.cueOut = entry.data;
  26317. },
  26318. 'cue-out-cont': function cueOutCont() {
  26319. currentUri.cueOutCont = entry.data;
  26320. },
  26321. 'cue-in': function cueIn() {
  26322. currentUri.cueIn = entry.data;
  26323. }
  26324. })[entry.tagType] || noop).call(self);
  26325. },
  26326. uri: function uri() {
  26327. currentUri.uri = entry.uri;
  26328. uris.push(currentUri); // if no explicit duration was declared, use the target duration
  26329. if (this.manifest.targetDuration && !('duration' in currentUri)) {
  26330. this.trigger('warn', {
  26331. message: 'defaulting segment duration to the target duration'
  26332. });
  26333. currentUri.duration = this.manifest.targetDuration;
  26334. } // annotate with encryption information, if necessary
  26335. if (_key) {
  26336. currentUri.key = _key;
  26337. }
  26338. currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
  26339. if (currentMap) {
  26340. currentUri.map = currentMap;
  26341. } // prepare for the next URI
  26342. currentUri = {};
  26343. },
  26344. comment: function comment() {// comments are not important for playback
  26345. },
  26346. custom: function custom() {
  26347. // if this is segment-level data attach the output to the segment
  26348. if (entry.segment) {
  26349. currentUri.custom = currentUri.custom || {};
  26350. currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
  26351. } else {
  26352. this.manifest.custom = this.manifest.custom || {};
  26353. this.manifest.custom[entry.customType] = entry.data;
  26354. }
  26355. }
  26356. })[entry.type].call(self);
  26357. });
  26358. return _this;
  26359. }
  26360. /**
  26361. * Parse the input string and update the manifest object.
  26362. *
  26363. * @param {string} chunk a potentially incomplete portion of the manifest
  26364. */
  26365. var _proto = Parser.prototype;
  26366. _proto.push = function push(chunk) {
  26367. this.lineStream.push(chunk);
  26368. };
  26369. /**
  26370. * Flush any remaining input. This can be handy if the last line of an M3U8
  26371. * manifest did not contain a trailing newline but the file has been
  26372. * completely received.
  26373. */
  26374. _proto.end = function end() {
  26375. // flush any buffered input
  26376. this.lineStream.push('\n');
  26377. };
  26378. /**
  26379. * Add an additional parser for non-standard tags
  26380. *
  26381. * @param {Object} options a map of options for the added parser
  26382. * @param {RegExp} options.expression a regular expression to match the custom header
  26383. * @param {string} options.type the type to register to the output
  26384. * @param {Function} [options.dataParser] function to parse the line into an object
  26385. * @param {boolean} [options.segment] should tag data be attached to the segment object
  26386. */
  26387. _proto.addParser = function addParser(options) {
  26388. this.parseStream.addParser(options);
  26389. };
  26390. /**
  26391. * Add a custom header mapper
  26392. *
  26393. * @param {Object} options
  26394. * @param {RegExp} options.expression a regular expression to match the custom header
  26395. * @param {Function} options.map function to translate tag into a different tag
  26396. */
  26397. _proto.addTagMapper = function addTagMapper(options) {
  26398. this.parseStream.addTagMapper(options);
  26399. };
  26400. return Parser;
  26401. }(Stream);
  26402. /*! @name mpd-parser @version 0.8.1 @license Apache-2.0 */
  26403. var isObject$1 = function isObject(obj) {
  26404. return !!obj && typeof obj === 'object';
  26405. };
  26406. var merge = function merge() {
  26407. for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) {
  26408. objects[_key] = arguments[_key];
  26409. }
  26410. return objects.reduce(function (result, source) {
  26411. Object.keys(source).forEach(function (key) {
  26412. if (Array.isArray(result[key]) && Array.isArray(source[key])) {
  26413. result[key] = result[key].concat(source[key]);
  26414. } else if (isObject$1(result[key]) && isObject$1(source[key])) {
  26415. result[key] = merge(result[key], source[key]);
  26416. } else {
  26417. result[key] = source[key];
  26418. }
  26419. });
  26420. return result;
  26421. }, {});
  26422. };
  26423. var values = function values(o) {
  26424. return Object.keys(o).map(function (k) {
  26425. return o[k];
  26426. });
  26427. };
  26428. var range = function range(start, end) {
  26429. var result = [];
  26430. for (var i = start; i < end; i++) {
  26431. result.push(i);
  26432. }
  26433. return result;
  26434. };
  26435. var flatten = function flatten(lists) {
  26436. return lists.reduce(function (x, y) {
  26437. return x.concat(y);
  26438. }, []);
  26439. };
  26440. var from = function from(list) {
  26441. if (!list.length) {
  26442. return [];
  26443. }
  26444. var result = [];
  26445. for (var i = 0; i < list.length; i++) {
  26446. result.push(list[i]);
  26447. }
  26448. return result;
  26449. };
  26450. var findIndexes = function findIndexes(l, key) {
  26451. return l.reduce(function (a, e, i) {
  26452. if (e[key]) {
  26453. a.push(i);
  26454. }
  26455. return a;
  26456. }, []);
  26457. };
  26458. var errors = {
  26459. INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
  26460. DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
  26461. DASH_INVALID_XML: 'DASH_INVALID_XML',
  26462. NO_BASE_URL: 'NO_BASE_URL',
  26463. MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
  26464. SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
  26465. UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
  26466. };
  26467. function createCommonjsModule$1(fn, module) {
  26468. return module = {
  26469. exports: {}
  26470. }, fn(module, module.exports), module.exports;
  26471. }
  26472. var urlToolkit$1 = createCommonjsModule$1(function (module, exports) {
  26473. // see https://tools.ietf.org/html/rfc1808
  26474. /* jshint ignore:start */
  26475. (function (root) {
  26476. /* jshint ignore:end */
  26477. var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
  26478. var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
  26479. var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
  26480. var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
  26481. var URLToolkit = {
  26482. // jshint ignore:line
  26483. // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
  26484. // E.g
  26485. // With opts.alwaysNormalize = false (default, spec compliant)
  26486. // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
  26487. // With opts.alwaysNormalize = true (not spec compliant)
  26488. // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
  26489. buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
  26490. opts = opts || {}; // remove any remaining space and CRLF
  26491. baseURL = baseURL.trim();
  26492. relativeURL = relativeURL.trim();
  26493. if (!relativeURL) {
  26494. // 2a) If the embedded URL is entirely empty, it inherits the
  26495. // entire base URL (i.e., is set equal to the base URL)
  26496. // and we are done.
  26497. if (!opts.alwaysNormalize) {
  26498. return baseURL;
  26499. }
  26500. var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  26501. if (!basePartsForNormalise) {
  26502. throw new Error('Error trying to parse base URL.');
  26503. }
  26504. basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
  26505. return URLToolkit.buildURLFromParts(basePartsForNormalise);
  26506. }
  26507. var relativeParts = URLToolkit.parseURL(relativeURL);
  26508. if (!relativeParts) {
  26509. throw new Error('Error trying to parse relative URL.');
  26510. }
  26511. if (relativeParts.scheme) {
  26512. // 2b) If the embedded URL starts with a scheme name, it is
  26513. // interpreted as an absolute URL and we are done.
  26514. if (!opts.alwaysNormalize) {
  26515. return relativeURL;
  26516. }
  26517. relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  26518. return URLToolkit.buildURLFromParts(relativeParts);
  26519. }
  26520. var baseParts = URLToolkit.parseURL(baseURL);
  26521. if (!baseParts) {
  26522. throw new Error('Error trying to parse base URL.');
  26523. }
  26524. if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  26525. // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
  26526. // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
  26527. var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  26528. baseParts.netLoc = pathParts[1];
  26529. baseParts.path = pathParts[2];
  26530. }
  26531. if (baseParts.netLoc && !baseParts.path) {
  26532. baseParts.path = '/';
  26533. }
  26534. var builtParts = {
  26535. // 2c) Otherwise, the embedded URL inherits the scheme of
  26536. // the base URL.
  26537. scheme: baseParts.scheme,
  26538. netLoc: relativeParts.netLoc,
  26539. path: null,
  26540. params: relativeParts.params,
  26541. query: relativeParts.query,
  26542. fragment: relativeParts.fragment
  26543. };
  26544. if (!relativeParts.netLoc) {
  26545. // 3) If the embedded URL's <net_loc> is non-empty, we skip to
  26546. // Step 7. Otherwise, the embedded URL inherits the <net_loc>
  26547. // (if any) of the base URL.
  26548. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
  26549. // path is not relative and we skip to Step 7.
  26550. if (relativeParts.path[0] !== '/') {
  26551. if (!relativeParts.path) {
  26552. // 5) If the embedded URL path is empty (and not preceded by a
  26553. // slash), then the embedded URL inherits the base URL path
  26554. builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
  26555. // step 7; otherwise, it inherits the <params> of the base
  26556. // URL (if any) and
  26557. if (!relativeParts.params) {
  26558. builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
  26559. // step 7; otherwise, it inherits the <query> of the base
  26560. // URL (if any) and we skip to step 7.
  26561. if (!relativeParts.query) {
  26562. builtParts.query = baseParts.query;
  26563. }
  26564. }
  26565. } else {
  26566. // 6) The last segment of the base URL's path (anything
  26567. // following the rightmost slash "/", or the entire path if no
  26568. // slash is present) is removed and the embedded URL's path is
  26569. // appended in its place.
  26570. var baseURLPath = baseParts.path;
  26571. var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
  26572. builtParts.path = URLToolkit.normalizePath(newPath);
  26573. }
  26574. }
  26575. }
  26576. if (builtParts.path === null) {
  26577. builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
  26578. }
  26579. return URLToolkit.buildURLFromParts(builtParts);
  26580. },
  26581. parseURL: function parseURL(url) {
  26582. var parts = URL_REGEX.exec(url);
  26583. if (!parts) {
  26584. return null;
  26585. }
  26586. return {
  26587. scheme: parts[1] || '',
  26588. netLoc: parts[2] || '',
  26589. path: parts[3] || '',
  26590. params: parts[4] || '',
  26591. query: parts[5] || '',
  26592. fragment: parts[6] || ''
  26593. };
  26594. },
  26595. normalizePath: function normalizePath(path) {
  26596. // The following operations are
  26597. // then applied, in order, to the new path:
  26598. // 6a) All occurrences of "./", where "." is a complete path
  26599. // segment, are removed.
  26600. // 6b) If the path ends with "." as a complete path segment,
  26601. // that "." is removed.
  26602. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
  26603. // complete path segment not equal to "..", are removed.
  26604. // Removal of these path segments is performed iteratively,
  26605. // removing the leftmost matching pattern on each iteration,
  26606. // until no matching pattern remains.
  26607. // 6d) If the path ends with "<segment>/..", where <segment> is a
  26608. // complete path segment not equal to "..", that
  26609. // "<segment>/.." is removed.
  26610. while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
  26611. return path.split('').reverse().join('');
  26612. },
  26613. buildURLFromParts: function buildURLFromParts(parts) {
  26614. return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
  26615. }
  26616. };
  26617. /* jshint ignore:start */
  26618. module.exports = URLToolkit;
  26619. })();
  26620. /* jshint ignore:end */
  26621. });
  26622. var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
  26623. // return early if we don't need to resolve
  26624. if (/^[a-z]+:/i.test(relativeUrl)) {
  26625. return relativeUrl;
  26626. } // if the base URL is relative then combine with the current location
  26627. if (!/\/\//i.test(baseUrl)) {
  26628. baseUrl = urlToolkit$1.buildAbsoluteURL(window$1.location.href, baseUrl);
  26629. }
  26630. return urlToolkit$1.buildAbsoluteURL(baseUrl, relativeUrl);
  26631. };
  26632. /**
  26633. * @typedef {Object} SingleUri
  26634. * @property {string} uri - relative location of segment
  26635. * @property {string} resolvedUri - resolved location of segment
  26636. * @property {Object} byterange - Object containing information on how to make byte range
  26637. * requests following byte-range-spec per RFC2616.
  26638. * @property {String} byterange.length - length of range request
  26639. * @property {String} byterange.offset - byte offset of range request
  26640. *
  26641. * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
  26642. */
  26643. /**
  26644. * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
  26645. * that conforms to how m3u8-parser is structured
  26646. *
  26647. * @see https://github.com/videojs/m3u8-parser
  26648. *
  26649. * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
  26650. * @param {string} source - source url for segment
  26651. * @param {string} range - optional range used for range calls,
  26652. * follows RFC 2616, Clause 14.35.1
  26653. * @return {SingleUri} full segment information transformed into a format similar
  26654. * to m3u8-parser
  26655. */
  26656. var urlTypeToSegment = function urlTypeToSegment(_ref) {
  26657. var _ref$baseUrl = _ref.baseUrl,
  26658. baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl,
  26659. _ref$source = _ref.source,
  26660. source = _ref$source === void 0 ? '' : _ref$source,
  26661. _ref$range = _ref.range,
  26662. range = _ref$range === void 0 ? '' : _ref$range,
  26663. _ref$indexRange = _ref.indexRange,
  26664. indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange;
  26665. var segment = {
  26666. uri: source,
  26667. resolvedUri: resolveUrl(baseUrl || '', source)
  26668. };
  26669. if (range || indexRange) {
  26670. var rangeStr = range ? range : indexRange;
  26671. var ranges = rangeStr.split('-');
  26672. var startRange = parseInt(ranges[0], 10);
  26673. var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to
  26674. // RFC 2616, Clause 14.35.1
  26675. segment.byterange = {
  26676. length: endRange - startRange + 1,
  26677. offset: startRange
  26678. };
  26679. }
  26680. return segment;
  26681. };
  26682. var byteRangeToString = function byteRangeToString(byterange) {
  26683. // `endRange` is one less than `offset + length` because the HTTP range
  26684. // header uses inclusive ranges
  26685. var endRange = byterange.offset + byterange.length - 1;
  26686. return byterange.offset + "-" + endRange;
  26687. };
  26688. /**
  26689. * Functions for calculating the range of available segments in static and dynamic
  26690. * manifests.
  26691. */
  26692. var segmentRange = {
  26693. /**
  26694. * Returns the entire range of available segments for a static MPD
  26695. *
  26696. * @param {Object} attributes
  26697. * Inheritied MPD attributes
  26698. * @return {{ start: number, end: number }}
  26699. * The start and end numbers for available segments
  26700. */
  26701. "static": function _static(attributes) {
  26702. var duration = attributes.duration,
  26703. _attributes$timescale = attributes.timescale,
  26704. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  26705. sourceDuration = attributes.sourceDuration;
  26706. return {
  26707. start: 0,
  26708. end: Math.ceil(sourceDuration / (duration / timescale))
  26709. };
  26710. },
  26711. /**
  26712. * Returns the current live window range of available segments for a dynamic MPD
  26713. *
  26714. * @param {Object} attributes
  26715. * Inheritied MPD attributes
  26716. * @return {{ start: number, end: number }}
  26717. * The start and end numbers for available segments
  26718. */
  26719. dynamic: function dynamic(attributes) {
  26720. var NOW = attributes.NOW,
  26721. clientOffset = attributes.clientOffset,
  26722. availabilityStartTime = attributes.availabilityStartTime,
  26723. _attributes$timescale2 = attributes.timescale,
  26724. timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
  26725. duration = attributes.duration,
  26726. _attributes$start = attributes.start,
  26727. start = _attributes$start === void 0 ? 0 : _attributes$start,
  26728. _attributes$minimumUp = attributes.minimumUpdatePeriod,
  26729. minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp,
  26730. _attributes$timeShift = attributes.timeShiftBufferDepth,
  26731. timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift;
  26732. var now = (NOW + clientOffset) / 1000;
  26733. var periodStartWC = availabilityStartTime + start;
  26734. var periodEndWC = now + minimumUpdatePeriod;
  26735. var periodDuration = periodEndWC - periodStartWC;
  26736. var segmentCount = Math.ceil(periodDuration * timescale / duration);
  26737. var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
  26738. var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
  26739. return {
  26740. start: Math.max(0, availableStart),
  26741. end: Math.min(segmentCount, availableEnd)
  26742. };
  26743. }
  26744. };
  26745. /**
  26746. * Maps a range of numbers to objects with information needed to build the corresponding
  26747. * segment list
  26748. *
  26749. * @name toSegmentsCallback
  26750. * @function
  26751. * @param {number} number
  26752. * Number of the segment
  26753. * @param {number} index
  26754. * Index of the number in the range list
  26755. * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
  26756. * Object with segment timing and duration info
  26757. */
  26758. /**
  26759. * Returns a callback for Array.prototype.map for mapping a range of numbers to
  26760. * information needed to build the segment list.
  26761. *
  26762. * @param {Object} attributes
  26763. * Inherited MPD attributes
  26764. * @return {toSegmentsCallback}
  26765. * Callback map function
  26766. */
  26767. var toSegments = function toSegments(attributes) {
  26768. return function (number, index) {
  26769. var duration = attributes.duration,
  26770. _attributes$timescale3 = attributes.timescale,
  26771. timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3,
  26772. periodIndex = attributes.periodIndex,
  26773. _attributes$startNumb = attributes.startNumber,
  26774. startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb;
  26775. return {
  26776. number: startNumber + number,
  26777. duration: duration / timescale,
  26778. timeline: periodIndex,
  26779. time: index * duration
  26780. };
  26781. };
  26782. };
  26783. /**
  26784. * Returns a list of objects containing segment timing and duration info used for
  26785. * building the list of segments. This uses the @duration attribute specified
  26786. * in the MPD manifest to derive the range of segments.
  26787. *
  26788. * @param {Object} attributes
  26789. * Inherited MPD attributes
  26790. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  26791. * List of Objects with segment timing and duration info
  26792. */
  26793. var parseByDuration = function parseByDuration(attributes) {
  26794. var _attributes$type = attributes.type,
  26795. type = _attributes$type === void 0 ? 'static' : _attributes$type,
  26796. duration = attributes.duration,
  26797. _attributes$timescale4 = attributes.timescale,
  26798. timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4,
  26799. sourceDuration = attributes.sourceDuration;
  26800. var _segmentRange$type = segmentRange[type](attributes),
  26801. start = _segmentRange$type.start,
  26802. end = _segmentRange$type.end;
  26803. var segments = range(start, end).map(toSegments(attributes));
  26804. if (type === 'static') {
  26805. var index = segments.length - 1; // final segment may be less than full segment duration
  26806. segments[index].duration = sourceDuration - duration / timescale * index;
  26807. }
  26808. return segments;
  26809. };
  26810. /**
  26811. * Translates SegmentBase into a set of segments.
  26812. * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
  26813. * node should be translated into segment.
  26814. *
  26815. * @param {Object} attributes
  26816. * Object containing all inherited attributes from parent elements with attribute
  26817. * names as keys
  26818. * @return {Object.<Array>} list of segments
  26819. */
  26820. var segmentsFromBase = function segmentsFromBase(attributes) {
  26821. var baseUrl = attributes.baseUrl,
  26822. _attributes$initializ = attributes.initialization,
  26823. initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ,
  26824. sourceDuration = attributes.sourceDuration,
  26825. _attributes$timescale = attributes.timescale,
  26826. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  26827. _attributes$indexRang = attributes.indexRange,
  26828. indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang,
  26829. duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
  26830. if (!baseUrl) {
  26831. throw new Error(errors.NO_BASE_URL);
  26832. }
  26833. var initSegment = urlTypeToSegment({
  26834. baseUrl: baseUrl,
  26835. source: initialization.sourceURL,
  26836. range: initialization.range
  26837. });
  26838. var segment = urlTypeToSegment({
  26839. baseUrl: baseUrl,
  26840. source: baseUrl,
  26841. indexRange: indexRange
  26842. });
  26843. segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
  26844. // (since SegmentBase is only for one total segment)
  26845. if (duration) {
  26846. var segmentTimeInfo = parseByDuration(attributes);
  26847. if (segmentTimeInfo.length) {
  26848. segment.duration = segmentTimeInfo[0].duration;
  26849. segment.timeline = segmentTimeInfo[0].timeline;
  26850. }
  26851. } else if (sourceDuration) {
  26852. segment.duration = sourceDuration / timescale;
  26853. segment.timeline = 0;
  26854. } // This is used for mediaSequence
  26855. segment.number = 0;
  26856. return [segment];
  26857. };
  26858. /**
  26859. * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
  26860. * according to the sidx information given.
  26861. *
  26862. * playlist.sidx has metadadata about the sidx where-as the sidx param
  26863. * is the parsed sidx box itself.
  26864. *
  26865. * @param {Object} playlist the playlist to update the sidx information for
  26866. * @param {Object} sidx the parsed sidx box
  26867. * @return {Object} the playlist object with the updated sidx information
  26868. */
  26869. var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) {
  26870. // Retain init segment information
  26871. var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing
  26872. var sourceDuration = playlist.sidx.duration; // Retain source timeline
  26873. var timeline = playlist.timeline || 0;
  26874. var sidxByteRange = playlist.sidx.byterange;
  26875. var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
  26876. var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
  26877. var mediaReferences = sidx.references.filter(function (r) {
  26878. return r.referenceType !== 1;
  26879. });
  26880. var segments = []; // firstOffset is the offset from the end of the sidx box
  26881. var startIndex = sidxEnd + sidx.firstOffset;
  26882. for (var i = 0; i < mediaReferences.length; i++) {
  26883. var reference = sidx.references[i]; // size of the referenced (sub)segment
  26884. var size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale
  26885. // this will be converted to seconds when generating segments
  26886. var duration = reference.subsegmentDuration; // should be an inclusive range
  26887. var endIndex = startIndex + size - 1;
  26888. var indexRange = startIndex + "-" + endIndex;
  26889. var attributes = {
  26890. baseUrl: baseUrl,
  26891. timescale: timescale,
  26892. timeline: timeline,
  26893. // this is used in parseByDuration
  26894. periodIndex: timeline,
  26895. duration: duration,
  26896. sourceDuration: sourceDuration,
  26897. indexRange: indexRange
  26898. };
  26899. var segment = segmentsFromBase(attributes)[0];
  26900. if (initSegment) {
  26901. segment.map = initSegment;
  26902. }
  26903. segments.push(segment);
  26904. startIndex += size;
  26905. }
  26906. playlist.segments = segments;
  26907. return playlist;
  26908. };
  26909. var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) {
  26910. var mergedPlaylists = values(playlists.reduce(function (acc, playlist) {
  26911. // assuming playlist IDs are the same across periods
  26912. // TODO: handle multiperiod where representation sets are not the same
  26913. // across periods
  26914. var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first
  26915. if (acc[name]) {
  26916. var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
  26917. if (playlist.segments[0]) {
  26918. playlist.segments[0].discontinuity = true;
  26919. }
  26920. (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content
  26921. // has the same contentProtection
  26922. if (playlist.attributes.contentProtection) {
  26923. acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
  26924. }
  26925. } else {
  26926. // first Period
  26927. acc[name] = playlist;
  26928. }
  26929. return acc;
  26930. }, {}));
  26931. return mergedPlaylists.map(function (playlist) {
  26932. playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity');
  26933. return playlist;
  26934. });
  26935. };
  26936. var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) {
  26937. if (sidxMapping === void 0) {
  26938. sidxMapping = {};
  26939. }
  26940. if (!Object.keys(sidxMapping).length) {
  26941. return playlists;
  26942. }
  26943. for (var i in playlists) {
  26944. var playlist = playlists[i];
  26945. if (!playlist.sidx) {
  26946. continue;
  26947. }
  26948. var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange);
  26949. var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
  26950. if (playlist.sidx && sidxMatch) {
  26951. addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri);
  26952. }
  26953. }
  26954. return playlists;
  26955. };
  26956. var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
  26957. var _attributes;
  26958. var attributes = _ref.attributes,
  26959. segments = _ref.segments,
  26960. sidx = _ref.sidx;
  26961. var playlist = {
  26962. attributes: (_attributes = {
  26963. NAME: attributes.id,
  26964. BANDWIDTH: attributes.bandwidth,
  26965. CODECS: attributes.codecs
  26966. }, _attributes['PROGRAM-ID'] = 1, _attributes),
  26967. uri: '',
  26968. endList: (attributes.type || 'static') === 'static',
  26969. timeline: attributes.periodIndex,
  26970. resolvedUri: '',
  26971. targetDuration: attributes.duration,
  26972. segments: segments,
  26973. mediaSequence: segments.length ? segments[0].number : 1
  26974. };
  26975. if (attributes.contentProtection) {
  26976. playlist.contentProtection = attributes.contentProtection;
  26977. }
  26978. if (sidx) {
  26979. playlist.sidx = sidx;
  26980. }
  26981. return playlist;
  26982. };
  26983. var formatVttPlaylist = function formatVttPlaylist(_ref2) {
  26984. var _attributes2;
  26985. var attributes = _ref2.attributes,
  26986. segments = _ref2.segments;
  26987. if (typeof segments === 'undefined') {
  26988. // vtt tracks may use single file in BaseURL
  26989. segments = [{
  26990. uri: attributes.baseUrl,
  26991. timeline: attributes.periodIndex,
  26992. resolvedUri: attributes.baseUrl || '',
  26993. duration: attributes.sourceDuration,
  26994. number: 0
  26995. }]; // targetDuration should be the same duration as the only segment
  26996. attributes.duration = attributes.sourceDuration;
  26997. }
  26998. return {
  26999. attributes: (_attributes2 = {
  27000. NAME: attributes.id,
  27001. BANDWIDTH: attributes.bandwidth
  27002. }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
  27003. uri: '',
  27004. endList: (attributes.type || 'static') === 'static',
  27005. timeline: attributes.periodIndex,
  27006. resolvedUri: attributes.baseUrl || '',
  27007. targetDuration: attributes.duration,
  27008. segments: segments,
  27009. mediaSequence: segments.length ? segments[0].number : 1
  27010. };
  27011. };
  27012. var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) {
  27013. if (sidxMapping === void 0) {
  27014. sidxMapping = {};
  27015. }
  27016. var mainPlaylist;
  27017. var formattedPlaylists = playlists.reduce(function (a, playlist) {
  27018. var role = playlist.attributes.role && playlist.attributes.role.value || '';
  27019. var language = playlist.attributes.lang || '';
  27020. var label = 'main';
  27021. if (language) {
  27022. var roleLabel = role ? " (" + role + ")" : '';
  27023. label = "" + playlist.attributes.lang + roleLabel;
  27024. } // skip if we already have the highest quality audio for a language
  27025. if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
  27026. return a;
  27027. }
  27028. a[label] = {
  27029. language: language,
  27030. autoselect: true,
  27031. "default": role === 'main',
  27032. playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping),
  27033. uri: ''
  27034. };
  27035. if (typeof mainPlaylist === 'undefined' && role === 'main') {
  27036. mainPlaylist = playlist;
  27037. mainPlaylist["default"] = true;
  27038. }
  27039. return a;
  27040. }, {}); // if no playlists have role "main", mark the first as main
  27041. if (!mainPlaylist) {
  27042. var firstLabel = Object.keys(formattedPlaylists)[0];
  27043. formattedPlaylists[firstLabel]["default"] = true;
  27044. }
  27045. return formattedPlaylists;
  27046. };
  27047. var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) {
  27048. if (sidxMapping === void 0) {
  27049. sidxMapping = {};
  27050. }
  27051. return playlists.reduce(function (a, playlist) {
  27052. var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles
  27053. if (a[label]) {
  27054. return a;
  27055. }
  27056. a[label] = {
  27057. language: label,
  27058. "default": false,
  27059. autoselect: false,
  27060. playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping),
  27061. uri: ''
  27062. };
  27063. return a;
  27064. }, {});
  27065. };
  27066. var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
  27067. var _attributes3;
  27068. var attributes = _ref3.attributes,
  27069. segments = _ref3.segments,
  27070. sidx = _ref3.sidx;
  27071. var playlist = {
  27072. attributes: (_attributes3 = {
  27073. NAME: attributes.id,
  27074. AUDIO: 'audio',
  27075. SUBTITLES: 'subs',
  27076. RESOLUTION: {
  27077. width: attributes.width,
  27078. height: attributes.height
  27079. },
  27080. CODECS: attributes.codecs,
  27081. BANDWIDTH: attributes.bandwidth
  27082. }, _attributes3['PROGRAM-ID'] = 1, _attributes3),
  27083. uri: '',
  27084. endList: (attributes.type || 'static') === 'static',
  27085. timeline: attributes.periodIndex,
  27086. resolvedUri: '',
  27087. targetDuration: attributes.duration,
  27088. segments: segments,
  27089. mediaSequence: segments.length ? segments[0].number : 1
  27090. };
  27091. if (attributes.contentProtection) {
  27092. playlist.contentProtection = attributes.contentProtection;
  27093. }
  27094. if (sidx) {
  27095. playlist.sidx = sidx;
  27096. }
  27097. return playlist;
  27098. };
  27099. var toM3u8 = function toM3u8(dashPlaylists, sidxMapping) {
  27100. var _mediaGroups;
  27101. if (sidxMapping === void 0) {
  27102. sidxMapping = {};
  27103. }
  27104. if (!dashPlaylists.length) {
  27105. return {};
  27106. } // grab all master attributes
  27107. var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
  27108. duration = _dashPlaylists$0$attr.sourceDuration,
  27109. _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod,
  27110. minimumUpdatePeriod = _dashPlaylists$0$attr2 === void 0 ? 0 : _dashPlaylists$0$attr2;
  27111. var videoOnly = function videoOnly(_ref4) {
  27112. var attributes = _ref4.attributes;
  27113. return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
  27114. };
  27115. var audioOnly = function audioOnly(_ref5) {
  27116. var attributes = _ref5.attributes;
  27117. return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
  27118. };
  27119. var vttOnly = function vttOnly(_ref6) {
  27120. var attributes = _ref6.attributes;
  27121. return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
  27122. };
  27123. var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
  27124. var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
  27125. var vttPlaylists = dashPlaylists.filter(vttOnly);
  27126. var master = {
  27127. allowCache: true,
  27128. discontinuityStarts: [],
  27129. segments: [],
  27130. endList: true,
  27131. mediaGroups: (_mediaGroups = {
  27132. AUDIO: {},
  27133. VIDEO: {}
  27134. }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
  27135. uri: '',
  27136. duration: duration,
  27137. playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping),
  27138. minimumUpdatePeriod: minimumUpdatePeriod * 1000
  27139. };
  27140. if (audioPlaylists.length) {
  27141. master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping);
  27142. }
  27143. if (vttPlaylists.length) {
  27144. master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
  27145. }
  27146. return master;
  27147. };
  27148. /**
  27149. * Calculates the R (repetition) value for a live stream (for the final segment
  27150. * in a manifest where the r value is negative 1)
  27151. *
  27152. * @param {Object} attributes
  27153. * Object containing all inherited attributes from parent elements with attribute
  27154. * names as keys
  27155. * @param {number} time
  27156. * current time (typically the total time up until the final segment)
  27157. * @param {number} duration
  27158. * duration property for the given <S />
  27159. *
  27160. * @return {number}
  27161. * R value to reach the end of the given period
  27162. */
  27163. var getLiveRValue = function getLiveRValue(attributes, time, duration) {
  27164. var NOW = attributes.NOW,
  27165. clientOffset = attributes.clientOffset,
  27166. availabilityStartTime = attributes.availabilityStartTime,
  27167. _attributes$timescale = attributes.timescale,
  27168. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  27169. _attributes$start = attributes.start,
  27170. start = _attributes$start === void 0 ? 0 : _attributes$start,
  27171. _attributes$minimumUp = attributes.minimumUpdatePeriod,
  27172. minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp;
  27173. var now = (NOW + clientOffset) / 1000;
  27174. var periodStartWC = availabilityStartTime + start;
  27175. var periodEndWC = now + minimumUpdatePeriod;
  27176. var periodDuration = periodEndWC - periodStartWC;
  27177. return Math.ceil((periodDuration * timescale - time) / duration);
  27178. };
  27179. /**
  27180. * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
  27181. * timing and duration
  27182. *
  27183. * @param {Object} attributes
  27184. * Object containing all inherited attributes from parent elements with attribute
  27185. * names as keys
  27186. * @param {Object[]} segmentTimeline
  27187. * List of objects representing the attributes of each S element contained within
  27188. *
  27189. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  27190. * List of Objects with segment timing and duration info
  27191. */
  27192. var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
  27193. var _attributes$type = attributes.type,
  27194. type = _attributes$type === void 0 ? 'static' : _attributes$type,
  27195. _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
  27196. minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2,
  27197. _attributes$media = attributes.media,
  27198. media = _attributes$media === void 0 ? '' : _attributes$media,
  27199. sourceDuration = attributes.sourceDuration,
  27200. _attributes$timescale2 = attributes.timescale,
  27201. timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
  27202. _attributes$startNumb = attributes.startNumber,
  27203. startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb,
  27204. timeline = attributes.periodIndex;
  27205. var segments = [];
  27206. var time = -1;
  27207. for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
  27208. var S = segmentTimeline[sIndex];
  27209. var duration = S.d;
  27210. var repeat = S.r || 0;
  27211. var segmentTime = S.t || 0;
  27212. if (time < 0) {
  27213. // first segment
  27214. time = segmentTime;
  27215. }
  27216. if (segmentTime && segmentTime > time) {
  27217. // discontinuity
  27218. // TODO: How to handle this type of discontinuity
  27219. // timeline++ here would treat it like HLS discontuity and content would
  27220. // get appended without gap
  27221. // E.G.
  27222. // <S t="0" d="1" />
  27223. // <S d="1" />
  27224. // <S d="1" />
  27225. // <S t="5" d="1" />
  27226. // would have $Time$ values of [0, 1, 2, 5]
  27227. // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
  27228. // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
  27229. // does the value of sourceDuration consider this when calculating arbitrary
  27230. // negative @r repeat value?
  27231. // E.G. Same elements as above with this added at the end
  27232. // <S d="1" r="-1" />
  27233. // with a sourceDuration of 10
  27234. // Would the 2 gaps be included in the time duration calculations resulting in
  27235. // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
  27236. // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
  27237. time = segmentTime;
  27238. }
  27239. var count = void 0;
  27240. if (repeat < 0) {
  27241. var nextS = sIndex + 1;
  27242. if (nextS === segmentTimeline.length) {
  27243. // last segment
  27244. if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
  27245. count = getLiveRValue(attributes, time, duration);
  27246. } else {
  27247. // TODO: This may be incorrect depending on conclusion of TODO above
  27248. count = (sourceDuration * timescale - time) / duration;
  27249. }
  27250. } else {
  27251. count = (segmentTimeline[nextS].t - time) / duration;
  27252. }
  27253. } else {
  27254. count = repeat + 1;
  27255. }
  27256. var end = startNumber + segments.length + count;
  27257. var number = startNumber + segments.length;
  27258. while (number < end) {
  27259. segments.push({
  27260. number: number,
  27261. duration: duration / timescale,
  27262. time: time,
  27263. timeline: timeline
  27264. });
  27265. time += duration;
  27266. number++;
  27267. }
  27268. }
  27269. return segments;
  27270. };
  27271. var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
  27272. /**
  27273. * Replaces template identifiers with corresponding values. To be used as the callback
  27274. * for String.prototype.replace
  27275. *
  27276. * @name replaceCallback
  27277. * @function
  27278. * @param {string} match
  27279. * Entire match of identifier
  27280. * @param {string} identifier
  27281. * Name of matched identifier
  27282. * @param {string} format
  27283. * Format tag string. Its presence indicates that padding is expected
  27284. * @param {string} width
  27285. * Desired length of the replaced value. Values less than this width shall be left
  27286. * zero padded
  27287. * @return {string}
  27288. * Replacement for the matched identifier
  27289. */
  27290. /**
  27291. * Returns a function to be used as a callback for String.prototype.replace to replace
  27292. * template identifiers
  27293. *
  27294. * @param {Obect} values
  27295. * Object containing values that shall be used to replace known identifiers
  27296. * @param {number} values.RepresentationID
  27297. * Value of the Representation@id attribute
  27298. * @param {number} values.Number
  27299. * Number of the corresponding segment
  27300. * @param {number} values.Bandwidth
  27301. * Value of the Representation@bandwidth attribute.
  27302. * @param {number} values.Time
  27303. * Timestamp value of the corresponding segment
  27304. * @return {replaceCallback}
  27305. * Callback to be used with String.prototype.replace to replace identifiers
  27306. */
  27307. var identifierReplacement = function identifierReplacement(values) {
  27308. return function (match, identifier, format, width) {
  27309. if (match === '$$') {
  27310. // escape sequence
  27311. return '$';
  27312. }
  27313. if (typeof values[identifier] === 'undefined') {
  27314. return match;
  27315. }
  27316. var value = '' + values[identifier];
  27317. if (identifier === 'RepresentationID') {
  27318. // Format tag shall not be present with RepresentationID
  27319. return value;
  27320. }
  27321. if (!format) {
  27322. width = 1;
  27323. } else {
  27324. width = parseInt(width, 10);
  27325. }
  27326. if (value.length >= width) {
  27327. return value;
  27328. }
  27329. return "" + new Array(width - value.length + 1).join('0') + value;
  27330. };
  27331. };
  27332. /**
  27333. * Constructs a segment url from a template string
  27334. *
  27335. * @param {string} url
  27336. * Template string to construct url from
  27337. * @param {Obect} values
  27338. * Object containing values that shall be used to replace known identifiers
  27339. * @param {number} values.RepresentationID
  27340. * Value of the Representation@id attribute
  27341. * @param {number} values.Number
  27342. * Number of the corresponding segment
  27343. * @param {number} values.Bandwidth
  27344. * Value of the Representation@bandwidth attribute.
  27345. * @param {number} values.Time
  27346. * Timestamp value of the corresponding segment
  27347. * @return {string}
  27348. * Segment url with identifiers replaced
  27349. */
  27350. var constructTemplateUrl = function constructTemplateUrl(url, values) {
  27351. return url.replace(identifierPattern, identifierReplacement(values));
  27352. };
  27353. /**
  27354. * Generates a list of objects containing timing and duration information about each
  27355. * segment needed to generate segment uris and the complete segment object
  27356. *
  27357. * @param {Object} attributes
  27358. * Object containing all inherited attributes from parent elements with attribute
  27359. * names as keys
  27360. * @param {Object[]|undefined} segmentTimeline
  27361. * List of objects representing the attributes of each S element contained within
  27362. * the SegmentTimeline element
  27363. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  27364. * List of Objects with segment timing and duration info
  27365. */
  27366. var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
  27367. if (!attributes.duration && !segmentTimeline) {
  27368. // if neither @duration or SegmentTimeline are present, then there shall be exactly
  27369. // one media segment
  27370. return [{
  27371. number: attributes.startNumber || 1,
  27372. duration: attributes.sourceDuration,
  27373. time: 0,
  27374. timeline: attributes.periodIndex
  27375. }];
  27376. }
  27377. if (attributes.duration) {
  27378. return parseByDuration(attributes);
  27379. }
  27380. return parseByTimeline(attributes, segmentTimeline);
  27381. };
  27382. /**
  27383. * Generates a list of segments using information provided by the SegmentTemplate element
  27384. *
  27385. * @param {Object} attributes
  27386. * Object containing all inherited attributes from parent elements with attribute
  27387. * names as keys
  27388. * @param {Object[]|undefined} segmentTimeline
  27389. * List of objects representing the attributes of each S element contained within
  27390. * the SegmentTimeline element
  27391. * @return {Object[]}
  27392. * List of segment objects
  27393. */
  27394. var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
  27395. var templateValues = {
  27396. RepresentationID: attributes.id,
  27397. Bandwidth: attributes.bandwidth || 0
  27398. };
  27399. var _attributes$initializ = attributes.initialization,
  27400. initialization = _attributes$initializ === void 0 ? {
  27401. sourceURL: '',
  27402. range: ''
  27403. } : _attributes$initializ;
  27404. var mapSegment = urlTypeToSegment({
  27405. baseUrl: attributes.baseUrl,
  27406. source: constructTemplateUrl(initialization.sourceURL, templateValues),
  27407. range: initialization.range
  27408. });
  27409. var segments = parseTemplateInfo(attributes, segmentTimeline);
  27410. return segments.map(function (segment) {
  27411. templateValues.Number = segment.number;
  27412. templateValues.Time = segment.time;
  27413. var uri = constructTemplateUrl(attributes.media || '', templateValues);
  27414. return {
  27415. uri: uri,
  27416. timeline: segment.timeline,
  27417. duration: segment.duration,
  27418. resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
  27419. map: mapSegment,
  27420. number: segment.number
  27421. };
  27422. });
  27423. };
  27424. /**
  27425. * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
  27426. * to an object that matches the output of a segment in videojs/mpd-parser
  27427. *
  27428. * @param {Object} attributes
  27429. * Object containing all inherited attributes from parent elements with attribute
  27430. * names as keys
  27431. * @param {Object} segmentUrl
  27432. * <SegmentURL> node to translate into a segment object
  27433. * @return {Object} translated segment object
  27434. */
  27435. var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
  27436. var baseUrl = attributes.baseUrl,
  27437. _attributes$initializ = attributes.initialization,
  27438. initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ;
  27439. var initSegment = urlTypeToSegment({
  27440. baseUrl: baseUrl,
  27441. source: initialization.sourceURL,
  27442. range: initialization.range
  27443. });
  27444. var segment = urlTypeToSegment({
  27445. baseUrl: baseUrl,
  27446. source: segmentUrl.media,
  27447. range: segmentUrl.mediaRange
  27448. });
  27449. segment.map = initSegment;
  27450. return segment;
  27451. };
  27452. /**
  27453. * Generates a list of segments using information provided by the SegmentList element
  27454. * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
  27455. * node should be translated into segment.
  27456. *
  27457. * @param {Object} attributes
  27458. * Object containing all inherited attributes from parent elements with attribute
  27459. * names as keys
  27460. * @param {Object[]|undefined} segmentTimeline
  27461. * List of objects representing the attributes of each S element contained within
  27462. * the SegmentTimeline element
  27463. * @return {Object.<Array>} list of segments
  27464. */
  27465. var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
  27466. var duration = attributes.duration,
  27467. _attributes$segmentUr = attributes.segmentUrls,
  27468. segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR
  27469. // if both SegmentTimeline and @duration are defined, it is outside of spec.
  27470. if (!duration && !segmentTimeline || duration && segmentTimeline) {
  27471. throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
  27472. }
  27473. var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
  27474. return SegmentURLToSegmentObject(attributes, segmentUrlObject);
  27475. });
  27476. var segmentTimeInfo;
  27477. if (duration) {
  27478. segmentTimeInfo = parseByDuration(attributes);
  27479. }
  27480. if (segmentTimeline) {
  27481. segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
  27482. }
  27483. var segments = segmentTimeInfo.map(function (segmentTime, index) {
  27484. if (segmentUrlMap[index]) {
  27485. var segment = segmentUrlMap[index];
  27486. segment.timeline = segmentTime.timeline;
  27487. segment.duration = segmentTime.duration;
  27488. segment.number = segmentTime.number;
  27489. return segment;
  27490. } // Since we're mapping we should get rid of any blank segments (in case
  27491. // the given SegmentTimeline is handling for more elements than we have
  27492. // SegmentURLs for).
  27493. }).filter(function (segment) {
  27494. return segment;
  27495. });
  27496. return segments;
  27497. };
  27498. var generateSegments = function generateSegments(_ref) {
  27499. var attributes = _ref.attributes,
  27500. segmentInfo = _ref.segmentInfo;
  27501. var segmentAttributes;
  27502. var segmentsFn;
  27503. if (segmentInfo.template) {
  27504. segmentsFn = segmentsFromTemplate;
  27505. segmentAttributes = merge(attributes, segmentInfo.template);
  27506. } else if (segmentInfo.base) {
  27507. segmentsFn = segmentsFromBase;
  27508. segmentAttributes = merge(attributes, segmentInfo.base);
  27509. } else if (segmentInfo.list) {
  27510. segmentsFn = segmentsFromList;
  27511. segmentAttributes = merge(attributes, segmentInfo.list);
  27512. }
  27513. var segmentsInfo = {
  27514. attributes: attributes
  27515. };
  27516. if (!segmentsFn) {
  27517. return segmentsInfo;
  27518. }
  27519. var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which
  27520. // must be in seconds. Since we've generated the segment list, we no longer need
  27521. // @duration to be in @timescale units, so we can convert it here.
  27522. if (segmentAttributes.duration) {
  27523. var _segmentAttributes = segmentAttributes,
  27524. duration = _segmentAttributes.duration,
  27525. _segmentAttributes$ti = _segmentAttributes.timescale,
  27526. timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti;
  27527. segmentAttributes.duration = duration / timescale;
  27528. } else if (segments.length) {
  27529. // if there is no @duration attribute, use the largest segment duration as
  27530. // as target duration
  27531. segmentAttributes.duration = segments.reduce(function (max, segment) {
  27532. return Math.max(max, Math.ceil(segment.duration));
  27533. }, 0);
  27534. } else {
  27535. segmentAttributes.duration = 0;
  27536. }
  27537. segmentsInfo.attributes = segmentAttributes;
  27538. segmentsInfo.segments = segments; // This is a sidx box without actual segment information
  27539. if (segmentInfo.base && segmentAttributes.indexRange) {
  27540. segmentsInfo.sidx = segments[0];
  27541. segmentsInfo.segments = [];
  27542. }
  27543. return segmentsInfo;
  27544. };
  27545. var toPlaylists = function toPlaylists(representations) {
  27546. return representations.map(generateSegments);
  27547. };
  27548. var findChildren = function findChildren(element, name) {
  27549. return from(element.childNodes).filter(function (_ref) {
  27550. var tagName = _ref.tagName;
  27551. return tagName === name;
  27552. });
  27553. };
  27554. var getContent = function getContent(element) {
  27555. return element.textContent.trim();
  27556. };
  27557. var parseDuration = function parseDuration(str) {
  27558. var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
  27559. var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
  27560. var SECONDS_IN_DAY = 24 * 60 * 60;
  27561. var SECONDS_IN_HOUR = 60 * 60;
  27562. var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
  27563. var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
  27564. var match = durationRegex.exec(str);
  27565. if (!match) {
  27566. return 0;
  27567. }
  27568. var _match$slice = match.slice(1),
  27569. year = _match$slice[0],
  27570. month = _match$slice[1],
  27571. day = _match$slice[2],
  27572. hour = _match$slice[3],
  27573. minute = _match$slice[4],
  27574. second = _match$slice[5];
  27575. 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);
  27576. };
  27577. var parseDate = function parseDate(str) {
  27578. // Date format without timezone according to ISO 8601
  27579. // YYY-MM-DDThh:mm:ss.ssssss
  27580. 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
  27581. // expressed by ending with 'Z'
  27582. if (dateRegex.test(str)) {
  27583. str += 'Z';
  27584. }
  27585. return Date.parse(str);
  27586. };
  27587. var parsers = {
  27588. /**
  27589. * Specifies the duration of the entire Media Presentation. Format is a duration string
  27590. * as specified in ISO 8601
  27591. *
  27592. * @param {string} value
  27593. * value of attribute as a string
  27594. * @return {number}
  27595. * The duration in seconds
  27596. */
  27597. mediaPresentationDuration: function mediaPresentationDuration(value) {
  27598. return parseDuration(value);
  27599. },
  27600. /**
  27601. * Specifies the Segment availability start time for all Segments referred to in this
  27602. * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
  27603. * time. Format is a date string as specified in ISO 8601
  27604. *
  27605. * @param {string} value
  27606. * value of attribute as a string
  27607. * @return {number}
  27608. * The date as seconds from unix epoch
  27609. */
  27610. availabilityStartTime: function availabilityStartTime(value) {
  27611. return parseDate(value) / 1000;
  27612. },
  27613. /**
  27614. * Specifies the smallest period between potential changes to the MPD. Format is a
  27615. * duration string as specified in ISO 8601
  27616. *
  27617. * @param {string} value
  27618. * value of attribute as a string
  27619. * @return {number}
  27620. * The duration in seconds
  27621. */
  27622. minimumUpdatePeriod: function minimumUpdatePeriod(value) {
  27623. return parseDuration(value);
  27624. },
  27625. /**
  27626. * Specifies the duration of the smallest time shifting buffer for any Representation
  27627. * in the MPD. Format is a duration string as specified in ISO 8601
  27628. *
  27629. * @param {string} value
  27630. * value of attribute as a string
  27631. * @return {number}
  27632. * The duration in seconds
  27633. */
  27634. timeShiftBufferDepth: function timeShiftBufferDepth(value) {
  27635. return parseDuration(value);
  27636. },
  27637. /**
  27638. * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
  27639. * Format is a duration string as specified in ISO 8601
  27640. *
  27641. * @param {string} value
  27642. * value of attribute as a string
  27643. * @return {number}
  27644. * The duration in seconds
  27645. */
  27646. start: function start(value) {
  27647. return parseDuration(value);
  27648. },
  27649. /**
  27650. * Specifies the width of the visual presentation
  27651. *
  27652. * @param {string} value
  27653. * value of attribute as a string
  27654. * @return {number}
  27655. * The parsed width
  27656. */
  27657. width: function width(value) {
  27658. return parseInt(value, 10);
  27659. },
  27660. /**
  27661. * Specifies the height of the visual presentation
  27662. *
  27663. * @param {string} value
  27664. * value of attribute as a string
  27665. * @return {number}
  27666. * The parsed height
  27667. */
  27668. height: function height(value) {
  27669. return parseInt(value, 10);
  27670. },
  27671. /**
  27672. * Specifies the bitrate of the representation
  27673. *
  27674. * @param {string} value
  27675. * value of attribute as a string
  27676. * @return {number}
  27677. * The parsed bandwidth
  27678. */
  27679. bandwidth: function bandwidth(value) {
  27680. return parseInt(value, 10);
  27681. },
  27682. /**
  27683. * Specifies the number of the first Media Segment in this Representation in the Period
  27684. *
  27685. * @param {string} value
  27686. * value of attribute as a string
  27687. * @return {number}
  27688. * The parsed number
  27689. */
  27690. startNumber: function startNumber(value) {
  27691. return parseInt(value, 10);
  27692. },
  27693. /**
  27694. * Specifies the timescale in units per seconds
  27695. *
  27696. * @param {string} value
  27697. * value of attribute as a string
  27698. * @return {number}
  27699. * The aprsed timescale
  27700. */
  27701. timescale: function timescale(value) {
  27702. return parseInt(value, 10);
  27703. },
  27704. /**
  27705. * Specifies the constant approximate Segment duration
  27706. * NOTE: The <Period> element also contains an @duration attribute. This duration
  27707. * specifies the duration of the Period. This attribute is currently not
  27708. * supported by the rest of the parser, however we still check for it to prevent
  27709. * errors.
  27710. *
  27711. * @param {string} value
  27712. * value of attribute as a string
  27713. * @return {number}
  27714. * The parsed duration
  27715. */
  27716. duration: function duration(value) {
  27717. var parsedValue = parseInt(value, 10);
  27718. if (isNaN(parsedValue)) {
  27719. return parseDuration(value);
  27720. }
  27721. return parsedValue;
  27722. },
  27723. /**
  27724. * Specifies the Segment duration, in units of the value of the @timescale.
  27725. *
  27726. * @param {string} value
  27727. * value of attribute as a string
  27728. * @return {number}
  27729. * The parsed duration
  27730. */
  27731. d: function d(value) {
  27732. return parseInt(value, 10);
  27733. },
  27734. /**
  27735. * Specifies the MPD start time, in @timescale units, the first Segment in the series
  27736. * starts relative to the beginning of the Period
  27737. *
  27738. * @param {string} value
  27739. * value of attribute as a string
  27740. * @return {number}
  27741. * The parsed time
  27742. */
  27743. t: function t(value) {
  27744. return parseInt(value, 10);
  27745. },
  27746. /**
  27747. * Specifies the repeat count of the number of following contiguous Segments with the
  27748. * same duration expressed by the value of @d
  27749. *
  27750. * @param {string} value
  27751. * value of attribute as a string
  27752. * @return {number}
  27753. * The parsed number
  27754. */
  27755. r: function r(value) {
  27756. return parseInt(value, 10);
  27757. },
  27758. /**
  27759. * Default parser for all other attributes. Acts as a no-op and just returns the value
  27760. * as a string
  27761. *
  27762. * @param {string} value
  27763. * value of attribute as a string
  27764. * @return {string}
  27765. * Unparsed value
  27766. */
  27767. DEFAULT: function DEFAULT(value) {
  27768. return value;
  27769. }
  27770. };
  27771. /**
  27772. * Gets all the attributes and values of the provided node, parses attributes with known
  27773. * types, and returns an object with attribute names mapped to values.
  27774. *
  27775. * @param {Node} el
  27776. * The node to parse attributes from
  27777. * @return {Object}
  27778. * Object with all attributes of el parsed
  27779. */
  27780. var parseAttributes$1 = function parseAttributes(el) {
  27781. if (!(el && el.attributes)) {
  27782. return {};
  27783. }
  27784. return from(el.attributes).reduce(function (a, e) {
  27785. var parseFn = parsers[e.name] || parsers.DEFAULT;
  27786. a[e.name] = parseFn(e.value);
  27787. return a;
  27788. }, {});
  27789. };
  27790. function decodeB64ToUint8Array(b64Text) {
  27791. var decodedString = window$1.atob(b64Text);
  27792. var array = new Uint8Array(decodedString.length);
  27793. for (var i = 0; i < decodedString.length; i++) {
  27794. array[i] = decodedString.charCodeAt(i);
  27795. }
  27796. return array;
  27797. }
  27798. var keySystemsMap = {
  27799. 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
  27800. 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
  27801. 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
  27802. 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
  27803. };
  27804. /**
  27805. * Builds a list of urls that is the product of the reference urls and BaseURL values
  27806. *
  27807. * @param {string[]} referenceUrls
  27808. * List of reference urls to resolve to
  27809. * @param {Node[]} baseUrlElements
  27810. * List of BaseURL nodes from the mpd
  27811. * @return {string[]}
  27812. * List of resolved urls
  27813. */
  27814. var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
  27815. if (!baseUrlElements.length) {
  27816. return referenceUrls;
  27817. }
  27818. return flatten(referenceUrls.map(function (reference) {
  27819. return baseUrlElements.map(function (baseUrlElement) {
  27820. return resolveUrl(reference, getContent(baseUrlElement));
  27821. });
  27822. }));
  27823. };
  27824. /**
  27825. * Contains all Segment information for its containing AdaptationSet
  27826. *
  27827. * @typedef {Object} SegmentInformation
  27828. * @property {Object|undefined} template
  27829. * Contains the attributes for the SegmentTemplate node
  27830. * @property {Object[]|undefined} timeline
  27831. * Contains a list of atrributes for each S node within the SegmentTimeline node
  27832. * @property {Object|undefined} list
  27833. * Contains the attributes for the SegmentList node
  27834. * @property {Object|undefined} base
  27835. * Contains the attributes for the SegmentBase node
  27836. */
  27837. /**
  27838. * Returns all available Segment information contained within the AdaptationSet node
  27839. *
  27840. * @param {Node} adaptationSet
  27841. * The AdaptationSet node to get Segment information from
  27842. * @return {SegmentInformation}
  27843. * The Segment information contained within the provided AdaptationSet
  27844. */
  27845. var getSegmentInformation = function getSegmentInformation(adaptationSet) {
  27846. var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
  27847. var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
  27848. var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
  27849. return merge({
  27850. tag: 'SegmentURL'
  27851. }, parseAttributes$1(s));
  27852. });
  27853. var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
  27854. var segmentTimelineParentNode = segmentList || segmentTemplate;
  27855. var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
  27856. var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
  27857. var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
  27858. // @initialization and an <Initialization> node. @initialization can be templated,
  27859. // while the node can have a url and range specified. If the <SegmentTemplate> has
  27860. // both @initialization and an <Initialization> subelement we opt to override with
  27861. // the node, as this interaction is not defined in the spec.
  27862. var template = segmentTemplate && parseAttributes$1(segmentTemplate);
  27863. if (template && segmentInitialization) {
  27864. template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization);
  27865. } else if (template && template.initialization) {
  27866. // If it is @initialization we convert it to an object since this is the format that
  27867. // later functions will rely on for the initialization segment. This is only valid
  27868. // for <SegmentTemplate>
  27869. template.initialization = {
  27870. sourceURL: template.initialization
  27871. };
  27872. }
  27873. var segmentInfo = {
  27874. template: template,
  27875. timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
  27876. return parseAttributes$1(s);
  27877. }),
  27878. list: segmentList && merge(parseAttributes$1(segmentList), {
  27879. segmentUrls: segmentUrls,
  27880. initialization: parseAttributes$1(segmentInitialization)
  27881. }),
  27882. base: segmentBase && merge(parseAttributes$1(segmentBase), {
  27883. initialization: parseAttributes$1(segmentInitialization)
  27884. })
  27885. };
  27886. Object.keys(segmentInfo).forEach(function (key) {
  27887. if (!segmentInfo[key]) {
  27888. delete segmentInfo[key];
  27889. }
  27890. });
  27891. return segmentInfo;
  27892. };
  27893. /**
  27894. * Contains Segment information and attributes needed to construct a Playlist object
  27895. * from a Representation
  27896. *
  27897. * @typedef {Object} RepresentationInformation
  27898. * @property {SegmentInformation} segmentInfo
  27899. * Segment information for this Representation
  27900. * @property {Object} attributes
  27901. * Inherited attributes for this Representation
  27902. */
  27903. /**
  27904. * Maps a Representation node to an object containing Segment information and attributes
  27905. *
  27906. * @name inheritBaseUrlsCallback
  27907. * @function
  27908. * @param {Node} representation
  27909. * Representation node from the mpd
  27910. * @return {RepresentationInformation}
  27911. * Representation information needed to construct a Playlist object
  27912. */
  27913. /**
  27914. * Returns a callback for Array.prototype.map for mapping Representation nodes to
  27915. * Segment information and attributes using inherited BaseURL nodes.
  27916. *
  27917. * @param {Object} adaptationSetAttributes
  27918. * Contains attributes inherited by the AdaptationSet
  27919. * @param {string[]} adaptationSetBaseUrls
  27920. * Contains list of resolved base urls inherited by the AdaptationSet
  27921. * @param {SegmentInformation} adaptationSetSegmentInfo
  27922. * Contains Segment information for the AdaptationSet
  27923. * @return {inheritBaseUrlsCallback}
  27924. * Callback map function
  27925. */
  27926. var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
  27927. return function (representation) {
  27928. var repBaseUrlElements = findChildren(representation, 'BaseURL');
  27929. var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
  27930. var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation));
  27931. var representationSegmentInfo = getSegmentInformation(representation);
  27932. return repBaseUrls.map(function (baseUrl) {
  27933. return {
  27934. segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
  27935. attributes: merge(attributes, {
  27936. baseUrl: baseUrl
  27937. })
  27938. };
  27939. });
  27940. };
  27941. };
  27942. /**
  27943. * Tranforms a series of content protection nodes to
  27944. * an object containing pssh data by key system
  27945. *
  27946. * @param {Node[]} contentProtectionNodes
  27947. * Content protection nodes
  27948. * @return {Object}
  27949. * Object containing pssh data by key system
  27950. */
  27951. var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
  27952. return contentProtectionNodes.reduce(function (acc, node) {
  27953. var attributes = parseAttributes$1(node);
  27954. var keySystem = keySystemsMap[attributes.schemeIdUri];
  27955. if (keySystem) {
  27956. acc[keySystem] = {
  27957. attributes: attributes
  27958. };
  27959. var psshNode = findChildren(node, 'cenc:pssh')[0];
  27960. if (psshNode) {
  27961. var pssh = getContent(psshNode);
  27962. var psshBuffer = pssh && decodeB64ToUint8Array(pssh);
  27963. acc[keySystem].pssh = psshBuffer;
  27964. }
  27965. }
  27966. return acc;
  27967. }, {});
  27968. };
  27969. /**
  27970. * Maps an AdaptationSet node to a list of Representation information objects
  27971. *
  27972. * @name toRepresentationsCallback
  27973. * @function
  27974. * @param {Node} adaptationSet
  27975. * AdaptationSet node from the mpd
  27976. * @return {RepresentationInformation[]}
  27977. * List of objects containing Representaion information
  27978. */
  27979. /**
  27980. * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
  27981. * Representation information objects
  27982. *
  27983. * @param {Object} periodAttributes
  27984. * Contains attributes inherited by the Period
  27985. * @param {string[]} periodBaseUrls
  27986. * Contains list of resolved base urls inherited by the Period
  27987. * @param {string[]} periodSegmentInfo
  27988. * Contains Segment Information at the period level
  27989. * @return {toRepresentationsCallback}
  27990. * Callback map function
  27991. */
  27992. var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
  27993. return function (adaptationSet) {
  27994. var adaptationSetAttributes = parseAttributes$1(adaptationSet);
  27995. var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
  27996. var role = findChildren(adaptationSet, 'Role')[0];
  27997. var roleAttributes = {
  27998. role: parseAttributes$1(role)
  27999. };
  28000. var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
  28001. var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
  28002. if (Object.keys(contentProtection).length) {
  28003. attrs = merge(attrs, {
  28004. contentProtection: contentProtection
  28005. });
  28006. }
  28007. var segmentInfo = getSegmentInformation(adaptationSet);
  28008. var representations = findChildren(adaptationSet, 'Representation');
  28009. var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
  28010. return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
  28011. };
  28012. };
  28013. /**
  28014. * Maps an Period node to a list of Representation inforamtion objects for all
  28015. * AdaptationSet nodes contained within the Period
  28016. *
  28017. * @name toAdaptationSetsCallback
  28018. * @function
  28019. * @param {Node} period
  28020. * Period node from the mpd
  28021. * @param {number} periodIndex
  28022. * Index of the Period within the mpd
  28023. * @return {RepresentationInformation[]}
  28024. * List of objects containing Representaion information
  28025. */
  28026. /**
  28027. * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
  28028. * Representation information objects
  28029. *
  28030. * @param {Object} mpdAttributes
  28031. * Contains attributes inherited by the mpd
  28032. * @param {string[]} mpdBaseUrls
  28033. * Contains list of resolved base urls inherited by the mpd
  28034. * @return {toAdaptationSetsCallback}
  28035. * Callback map function
  28036. */
  28037. var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
  28038. return function (period, index) {
  28039. var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
  28040. var periodAtt = parseAttributes$1(period);
  28041. var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number
  28042. var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId;
  28043. var periodAttributes = merge(mpdAttributes, {
  28044. periodIndex: periodIndex
  28045. });
  28046. var adaptationSets = findChildren(period, 'AdaptationSet');
  28047. var periodSegmentInfo = getSegmentInformation(period);
  28048. return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
  28049. };
  28050. };
  28051. /**
  28052. * Traverses the mpd xml tree to generate a list of Representation information objects
  28053. * that have inherited attributes from parent nodes
  28054. *
  28055. * @param {Node} mpd
  28056. * The root node of the mpd
  28057. * @param {Object} options
  28058. * Available options for inheritAttributes
  28059. * @param {string} options.manifestUri
  28060. * The uri source of the mpd
  28061. * @param {number} options.NOW
  28062. * Current time per DASH IOP. Default is current time in ms since epoch
  28063. * @param {number} options.clientOffset
  28064. * Client time difference from NOW (in milliseconds)
  28065. * @return {RepresentationInformation[]}
  28066. * List of objects containing Representation information
  28067. */
  28068. var inheritAttributes = function inheritAttributes(mpd, options) {
  28069. if (options === void 0) {
  28070. options = {};
  28071. }
  28072. var _options = options,
  28073. _options$manifestUri = _options.manifestUri,
  28074. manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri,
  28075. _options$NOW = _options.NOW,
  28076. NOW = _options$NOW === void 0 ? Date.now() : _options$NOW,
  28077. _options$clientOffset = _options.clientOffset,
  28078. clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
  28079. var periods = findChildren(mpd, 'Period');
  28080. if (!periods.length) {
  28081. throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
  28082. }
  28083. var mpdAttributes = parseAttributes$1(mpd);
  28084. var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL'));
  28085. mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
  28086. mpdAttributes.NOW = NOW;
  28087. mpdAttributes.clientOffset = clientOffset;
  28088. return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)));
  28089. };
  28090. var stringToMpdXml = function stringToMpdXml(manifestString) {
  28091. if (manifestString === '') {
  28092. throw new Error(errors.DASH_EMPTY_MANIFEST);
  28093. }
  28094. var parser = new window$1.DOMParser();
  28095. var xml = parser.parseFromString(manifestString, 'application/xml');
  28096. var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
  28097. if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
  28098. throw new Error(errors.DASH_INVALID_XML);
  28099. }
  28100. return mpd;
  28101. };
  28102. /**
  28103. * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
  28104. *
  28105. * @param {string} mpd
  28106. * XML string of the MPD manifest
  28107. * @return {Object|null}
  28108. * Attributes of UTCTiming node specified in the manifest. Null if none found
  28109. */
  28110. var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
  28111. var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
  28112. if (!UTCTimingNode) {
  28113. return null;
  28114. }
  28115. var attributes = parseAttributes$1(UTCTimingNode);
  28116. switch (attributes.schemeIdUri) {
  28117. case 'urn:mpeg:dash:utc:http-head:2014':
  28118. case 'urn:mpeg:dash:utc:http-head:2012':
  28119. attributes.method = 'HEAD';
  28120. break;
  28121. case 'urn:mpeg:dash:utc:http-xsdate:2014':
  28122. case 'urn:mpeg:dash:utc:http-iso:2014':
  28123. case 'urn:mpeg:dash:utc:http-xsdate:2012':
  28124. case 'urn:mpeg:dash:utc:http-iso:2012':
  28125. attributes.method = 'GET';
  28126. break;
  28127. case 'urn:mpeg:dash:utc:direct:2014':
  28128. case 'urn:mpeg:dash:utc:direct:2012':
  28129. attributes.method = 'DIRECT';
  28130. attributes.value = Date.parse(attributes.value);
  28131. break;
  28132. case 'urn:mpeg:dash:utc:http-ntp:2014':
  28133. case 'urn:mpeg:dash:utc:ntp:2014':
  28134. case 'urn:mpeg:dash:utc:sntp:2014':
  28135. default:
  28136. throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
  28137. }
  28138. return attributes;
  28139. };
  28140. var parse = function parse(manifestString, options) {
  28141. if (options === void 0) {
  28142. options = {};
  28143. }
  28144. return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)), options.sidxMapping);
  28145. };
  28146. /**
  28147. * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
  28148. *
  28149. * @param {string} manifestString
  28150. * XML string of the MPD manifest
  28151. * @return {Object|null}
  28152. * Attributes of UTCTiming node specified in the manifest. Null if none found
  28153. */
  28154. var parseUTCTiming = function parseUTCTiming(manifestString) {
  28155. return parseUTCTimingScheme(stringToMpdXml(manifestString));
  28156. };
  28157. /**
  28158. * mux.js
  28159. *
  28160. * Copyright (c) Brightcove
  28161. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  28162. */
  28163. var toUnsigned = function toUnsigned(value) {
  28164. return value >>> 0;
  28165. };
  28166. var bin = {
  28167. toUnsigned: toUnsigned
  28168. };
  28169. var toUnsigned$1 = bin.toUnsigned;
  28170. var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
  28171. _findBox = function findBox(data, path) {
  28172. var results = [],
  28173. i,
  28174. size,
  28175. type,
  28176. end,
  28177. subresults;
  28178. if (!path.length) {
  28179. // short-circuit the search for empty paths
  28180. return null;
  28181. }
  28182. for (i = 0; i < data.byteLength;) {
  28183. size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  28184. type = parseType(data.subarray(i + 4, i + 8));
  28185. end = size > 1 ? i + size : data.byteLength;
  28186. if (type === path[0]) {
  28187. if (path.length === 1) {
  28188. // this is the end of the path and we've found the box we were
  28189. // looking for
  28190. results.push(data.subarray(i + 8, end));
  28191. } else {
  28192. // recursively search for the next box along the path
  28193. subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
  28194. if (subresults.length) {
  28195. results = results.concat(subresults);
  28196. }
  28197. }
  28198. }
  28199. i = end;
  28200. } // we've finished searching all of data
  28201. return results;
  28202. };
  28203. /**
  28204. * Returns the string representation of an ASCII encoded four byte buffer.
  28205. * @param buffer {Uint8Array} a four-byte buffer to translate
  28206. * @return {string} the corresponding string
  28207. */
  28208. parseType = function parseType(buffer) {
  28209. var result = '';
  28210. result += String.fromCharCode(buffer[0]);
  28211. result += String.fromCharCode(buffer[1]);
  28212. result += String.fromCharCode(buffer[2]);
  28213. result += String.fromCharCode(buffer[3]);
  28214. return result;
  28215. };
  28216. /**
  28217. * Parses an MP4 initialization segment and extracts the timescale
  28218. * values for any declared tracks. Timescale values indicate the
  28219. * number of clock ticks per second to assume for time-based values
  28220. * elsewhere in the MP4.
  28221. *
  28222. * To determine the start time of an MP4, you need two pieces of
  28223. * information: the timescale unit and the earliest base media decode
  28224. * time. Multiple timescales can be specified within an MP4 but the
  28225. * base media decode time is always expressed in the timescale from
  28226. * the media header box for the track:
  28227. * ```
  28228. * moov > trak > mdia > mdhd.timescale
  28229. * ```
  28230. * @param init {Uint8Array} the bytes of the init segment
  28231. * @return {object} a hash of track ids to timescale values or null if
  28232. * the init segment is malformed.
  28233. */
  28234. timescale = function timescale(init) {
  28235. var result = {},
  28236. traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
  28237. return traks.reduce(function (result, trak) {
  28238. var tkhd, version, index, id, mdhd;
  28239. tkhd = _findBox(trak, ['tkhd'])[0];
  28240. if (!tkhd) {
  28241. return null;
  28242. }
  28243. version = tkhd[0];
  28244. index = version === 0 ? 12 : 20;
  28245. id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  28246. mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
  28247. if (!mdhd) {
  28248. return null;
  28249. }
  28250. version = mdhd[0];
  28251. index = version === 0 ? 12 : 20;
  28252. result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  28253. return result;
  28254. }, result);
  28255. };
  28256. /**
  28257. * Determine the base media decode start time, in seconds, for an MP4
  28258. * fragment. If multiple fragments are specified, the earliest time is
  28259. * returned.
  28260. *
  28261. * The base media decode time can be parsed from track fragment
  28262. * metadata:
  28263. * ```
  28264. * moof > traf > tfdt.baseMediaDecodeTime
  28265. * ```
  28266. * It requires the timescale value from the mdhd to interpret.
  28267. *
  28268. * @param timescale {object} a hash of track ids to timescale values.
  28269. * @return {number} the earliest base media decode start time for the
  28270. * fragment, in seconds
  28271. */
  28272. startTime = function startTime(timescale, fragment) {
  28273. var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
  28274. trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
  28275. baseTimes = [].concat.apply([], trafs.map(function (traf) {
  28276. return _findBox(traf, ['tfhd']).map(function (tfhd) {
  28277. var id, scale, baseTime; // get the track id from the tfhd
  28278. id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  28279. scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  28280. baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
  28281. var version, result;
  28282. version = tfdt[0];
  28283. result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
  28284. if (version === 1) {
  28285. result *= Math.pow(2, 32);
  28286. result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
  28287. }
  28288. return result;
  28289. })[0];
  28290. baseTime = baseTime || Infinity; // convert base time to seconds
  28291. return baseTime / scale;
  28292. });
  28293. })); // return the minimum
  28294. result = Math.min.apply(null, baseTimes);
  28295. return isFinite(result) ? result : 0;
  28296. };
  28297. /**
  28298. * Find the trackIds of the video tracks in this source.
  28299. * Found by parsing the Handler Reference and Track Header Boxes:
  28300. * moov > trak > mdia > hdlr
  28301. * moov > trak > tkhd
  28302. *
  28303. * @param {Uint8Array} init - The bytes of the init segment for this source
  28304. * @return {Number[]} A list of trackIds
  28305. *
  28306. * @see ISO-BMFF-12/2015, Section 8.4.3
  28307. **/
  28308. getVideoTrackIds = function getVideoTrackIds(init) {
  28309. var traks = _findBox(init, ['moov', 'trak']);
  28310. var videoTrackIds = [];
  28311. traks.forEach(function (trak) {
  28312. var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
  28313. var tkhds = _findBox(trak, ['tkhd']);
  28314. hdlrs.forEach(function (hdlr, index) {
  28315. var handlerType = parseType(hdlr.subarray(8, 12));
  28316. var tkhd = tkhds[index];
  28317. var view;
  28318. var version;
  28319. var trackId;
  28320. if (handlerType === 'vide') {
  28321. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  28322. version = view.getUint8(0);
  28323. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  28324. videoTrackIds.push(trackId);
  28325. }
  28326. });
  28327. });
  28328. return videoTrackIds;
  28329. };
  28330. var probe = {
  28331. findBox: _findBox,
  28332. parseType: parseType,
  28333. timescale: timescale,
  28334. startTime: startTime,
  28335. videoTrackIds: getVideoTrackIds
  28336. };
  28337. var inspectMp4,
  28338. _textifyMp,
  28339. parseType$1 = probe.parseType,
  28340. parseMp4Date = function parseMp4Date(seconds) {
  28341. return new Date(seconds * 1000 - 2082844800000);
  28342. },
  28343. parseSampleFlags = function parseSampleFlags(flags) {
  28344. return {
  28345. isLeading: (flags[0] & 0x0c) >>> 2,
  28346. dependsOn: flags[0] & 0x03,
  28347. isDependedOn: (flags[1] & 0xc0) >>> 6,
  28348. hasRedundancy: (flags[1] & 0x30) >>> 4,
  28349. paddingValue: (flags[1] & 0x0e) >>> 1,
  28350. isNonSyncSample: flags[1] & 0x01,
  28351. degradationPriority: flags[2] << 8 | flags[3]
  28352. };
  28353. },
  28354. nalParse = function nalParse(avcStream) {
  28355. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  28356. result = [],
  28357. i,
  28358. length;
  28359. for (i = 0; i + 4 < avcStream.length; i += length) {
  28360. length = avcView.getUint32(i);
  28361. i += 4; // bail if this doesn't appear to be an H264 stream
  28362. if (length <= 0) {
  28363. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  28364. continue;
  28365. }
  28366. switch (avcStream[i] & 0x1F) {
  28367. case 0x01:
  28368. result.push('slice_layer_without_partitioning_rbsp');
  28369. break;
  28370. case 0x05:
  28371. result.push('slice_layer_without_partitioning_rbsp_idr');
  28372. break;
  28373. case 0x06:
  28374. result.push('sei_rbsp');
  28375. break;
  28376. case 0x07:
  28377. result.push('seq_parameter_set_rbsp');
  28378. break;
  28379. case 0x08:
  28380. result.push('pic_parameter_set_rbsp');
  28381. break;
  28382. case 0x09:
  28383. result.push('access_unit_delimiter_rbsp');
  28384. break;
  28385. default:
  28386. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  28387. break;
  28388. }
  28389. }
  28390. return result;
  28391. },
  28392. // registry of handlers for individual mp4 box types
  28393. parse$1 = {
  28394. // codingname, not a first-class box type. stsd entries share the
  28395. // same format as real boxes so the parsing infrastructure can be
  28396. // shared
  28397. avc1: function avc1(data) {
  28398. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28399. return {
  28400. dataReferenceIndex: view.getUint16(6),
  28401. width: view.getUint16(24),
  28402. height: view.getUint16(26),
  28403. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  28404. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  28405. frameCount: view.getUint16(40),
  28406. depth: view.getUint16(74),
  28407. config: inspectMp4(data.subarray(78, data.byteLength))
  28408. };
  28409. },
  28410. avcC: function avcC(data) {
  28411. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28412. result = {
  28413. configurationVersion: data[0],
  28414. avcProfileIndication: data[1],
  28415. profileCompatibility: data[2],
  28416. avcLevelIndication: data[3],
  28417. lengthSizeMinusOne: data[4] & 0x03,
  28418. sps: [],
  28419. pps: []
  28420. },
  28421. numOfSequenceParameterSets = data[5] & 0x1f,
  28422. numOfPictureParameterSets,
  28423. nalSize,
  28424. offset,
  28425. i; // iterate past any SPSs
  28426. offset = 6;
  28427. for (i = 0; i < numOfSequenceParameterSets; i++) {
  28428. nalSize = view.getUint16(offset);
  28429. offset += 2;
  28430. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  28431. offset += nalSize;
  28432. } // iterate past any PPSs
  28433. numOfPictureParameterSets = data[offset];
  28434. offset++;
  28435. for (i = 0; i < numOfPictureParameterSets; i++) {
  28436. nalSize = view.getUint16(offset);
  28437. offset += 2;
  28438. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  28439. offset += nalSize;
  28440. }
  28441. return result;
  28442. },
  28443. btrt: function btrt(data) {
  28444. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28445. return {
  28446. bufferSizeDB: view.getUint32(0),
  28447. maxBitrate: view.getUint32(4),
  28448. avgBitrate: view.getUint32(8)
  28449. };
  28450. },
  28451. esds: function esds(data) {
  28452. return {
  28453. version: data[0],
  28454. flags: new Uint8Array(data.subarray(1, 4)),
  28455. esId: data[6] << 8 | data[7],
  28456. streamPriority: data[8] & 0x1f,
  28457. decoderConfig: {
  28458. objectProfileIndication: data[11],
  28459. streamType: data[12] >>> 2 & 0x3f,
  28460. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  28461. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  28462. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  28463. decoderConfigDescriptor: {
  28464. tag: data[24],
  28465. length: data[25],
  28466. audioObjectType: data[26] >>> 3 & 0x1f,
  28467. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  28468. channelConfiguration: data[27] >>> 3 & 0x0f
  28469. }
  28470. }
  28471. };
  28472. },
  28473. ftyp: function ftyp(data) {
  28474. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28475. result = {
  28476. majorBrand: parseType$1(data.subarray(0, 4)),
  28477. minorVersion: view.getUint32(4),
  28478. compatibleBrands: []
  28479. },
  28480. i = 8;
  28481. while (i < data.byteLength) {
  28482. result.compatibleBrands.push(parseType$1(data.subarray(i, i + 4)));
  28483. i += 4;
  28484. }
  28485. return result;
  28486. },
  28487. dinf: function dinf(data) {
  28488. return {
  28489. boxes: inspectMp4(data)
  28490. };
  28491. },
  28492. dref: function dref(data) {
  28493. return {
  28494. version: data[0],
  28495. flags: new Uint8Array(data.subarray(1, 4)),
  28496. dataReferences: inspectMp4(data.subarray(8))
  28497. };
  28498. },
  28499. hdlr: function hdlr(data) {
  28500. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28501. result = {
  28502. version: view.getUint8(0),
  28503. flags: new Uint8Array(data.subarray(1, 4)),
  28504. handlerType: parseType$1(data.subarray(8, 12)),
  28505. name: ''
  28506. },
  28507. i = 8; // parse out the name field
  28508. for (i = 24; i < data.byteLength; i++) {
  28509. if (data[i] === 0x00) {
  28510. // the name field is null-terminated
  28511. i++;
  28512. break;
  28513. }
  28514. result.name += String.fromCharCode(data[i]);
  28515. } // decode UTF-8 to javascript's internal representation
  28516. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  28517. result.name = decodeURIComponent(escape(result.name));
  28518. return result;
  28519. },
  28520. mdat: function mdat(data) {
  28521. return {
  28522. byteLength: data.byteLength,
  28523. nals: nalParse(data)
  28524. };
  28525. },
  28526. mdhd: function mdhd(data) {
  28527. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28528. i = 4,
  28529. language,
  28530. result = {
  28531. version: view.getUint8(0),
  28532. flags: new Uint8Array(data.subarray(1, 4)),
  28533. language: ''
  28534. };
  28535. if (result.version === 1) {
  28536. i += 4;
  28537. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28538. i += 8;
  28539. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28540. i += 4;
  28541. result.timescale = view.getUint32(i);
  28542. i += 8;
  28543. result.duration = view.getUint32(i); // truncating top 4 bytes
  28544. } else {
  28545. result.creationTime = parseMp4Date(view.getUint32(i));
  28546. i += 4;
  28547. result.modificationTime = parseMp4Date(view.getUint32(i));
  28548. i += 4;
  28549. result.timescale = view.getUint32(i);
  28550. i += 4;
  28551. result.duration = view.getUint32(i);
  28552. }
  28553. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  28554. // each field is the packed difference between its ASCII value and 0x60
  28555. language = view.getUint16(i);
  28556. result.language += String.fromCharCode((language >> 10) + 0x60);
  28557. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  28558. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  28559. return result;
  28560. },
  28561. mdia: function mdia(data) {
  28562. return {
  28563. boxes: inspectMp4(data)
  28564. };
  28565. },
  28566. mfhd: function mfhd(data) {
  28567. return {
  28568. version: data[0],
  28569. flags: new Uint8Array(data.subarray(1, 4)),
  28570. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  28571. };
  28572. },
  28573. minf: function minf(data) {
  28574. return {
  28575. boxes: inspectMp4(data)
  28576. };
  28577. },
  28578. // codingname, not a first-class box type. stsd entries share the
  28579. // same format as real boxes so the parsing infrastructure can be
  28580. // shared
  28581. mp4a: function mp4a(data) {
  28582. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28583. result = {
  28584. // 6 bytes reserved
  28585. dataReferenceIndex: view.getUint16(6),
  28586. // 4 + 4 bytes reserved
  28587. channelcount: view.getUint16(16),
  28588. samplesize: view.getUint16(18),
  28589. // 2 bytes pre_defined
  28590. // 2 bytes reserved
  28591. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  28592. }; // if there are more bytes to process, assume this is an ISO/IEC
  28593. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  28594. if (data.byteLength > 28) {
  28595. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  28596. }
  28597. return result;
  28598. },
  28599. moof: function moof(data) {
  28600. return {
  28601. boxes: inspectMp4(data)
  28602. };
  28603. },
  28604. moov: function moov(data) {
  28605. return {
  28606. boxes: inspectMp4(data)
  28607. };
  28608. },
  28609. mvex: function mvex(data) {
  28610. return {
  28611. boxes: inspectMp4(data)
  28612. };
  28613. },
  28614. mvhd: function mvhd(data) {
  28615. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28616. i = 4,
  28617. result = {
  28618. version: view.getUint8(0),
  28619. flags: new Uint8Array(data.subarray(1, 4))
  28620. };
  28621. if (result.version === 1) {
  28622. i += 4;
  28623. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28624. i += 8;
  28625. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28626. i += 4;
  28627. result.timescale = view.getUint32(i);
  28628. i += 8;
  28629. result.duration = view.getUint32(i); // truncating top 4 bytes
  28630. } else {
  28631. result.creationTime = parseMp4Date(view.getUint32(i));
  28632. i += 4;
  28633. result.modificationTime = parseMp4Date(view.getUint32(i));
  28634. i += 4;
  28635. result.timescale = view.getUint32(i);
  28636. i += 4;
  28637. result.duration = view.getUint32(i);
  28638. }
  28639. i += 4; // convert fixed-point, base 16 back to a number
  28640. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  28641. i += 4;
  28642. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  28643. i += 2;
  28644. i += 2;
  28645. i += 2 * 4;
  28646. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  28647. i += 9 * 4;
  28648. i += 6 * 4;
  28649. result.nextTrackId = view.getUint32(i);
  28650. return result;
  28651. },
  28652. pdin: function pdin(data) {
  28653. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28654. return {
  28655. version: view.getUint8(0),
  28656. flags: new Uint8Array(data.subarray(1, 4)),
  28657. rate: view.getUint32(4),
  28658. initialDelay: view.getUint32(8)
  28659. };
  28660. },
  28661. sdtp: function sdtp(data) {
  28662. var result = {
  28663. version: data[0],
  28664. flags: new Uint8Array(data.subarray(1, 4)),
  28665. samples: []
  28666. },
  28667. i;
  28668. for (i = 4; i < data.byteLength; i++) {
  28669. result.samples.push({
  28670. dependsOn: (data[i] & 0x30) >> 4,
  28671. isDependedOn: (data[i] & 0x0c) >> 2,
  28672. hasRedundancy: data[i] & 0x03
  28673. });
  28674. }
  28675. return result;
  28676. },
  28677. sidx: function sidx(data) {
  28678. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28679. result = {
  28680. version: data[0],
  28681. flags: new Uint8Array(data.subarray(1, 4)),
  28682. references: [],
  28683. referenceId: view.getUint32(4),
  28684. timescale: view.getUint32(8),
  28685. earliestPresentationTime: view.getUint32(12),
  28686. firstOffset: view.getUint32(16)
  28687. },
  28688. referenceCount = view.getUint16(22),
  28689. i;
  28690. for (i = 24; referenceCount; i += 12, referenceCount--) {
  28691. result.references.push({
  28692. referenceType: (data[i] & 0x80) >>> 7,
  28693. referencedSize: view.getUint32(i) & 0x7FFFFFFF,
  28694. subsegmentDuration: view.getUint32(i + 4),
  28695. startsWithSap: !!(data[i + 8] & 0x80),
  28696. sapType: (data[i + 8] & 0x70) >>> 4,
  28697. sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
  28698. });
  28699. }
  28700. return result;
  28701. },
  28702. smhd: function smhd(data) {
  28703. return {
  28704. version: data[0],
  28705. flags: new Uint8Array(data.subarray(1, 4)),
  28706. balance: data[4] + data[5] / 256
  28707. };
  28708. },
  28709. stbl: function stbl(data) {
  28710. return {
  28711. boxes: inspectMp4(data)
  28712. };
  28713. },
  28714. stco: function stco(data) {
  28715. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28716. result = {
  28717. version: data[0],
  28718. flags: new Uint8Array(data.subarray(1, 4)),
  28719. chunkOffsets: []
  28720. },
  28721. entryCount = view.getUint32(4),
  28722. i;
  28723. for (i = 8; entryCount; i += 4, entryCount--) {
  28724. result.chunkOffsets.push(view.getUint32(i));
  28725. }
  28726. return result;
  28727. },
  28728. stsc: function stsc(data) {
  28729. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28730. entryCount = view.getUint32(4),
  28731. result = {
  28732. version: data[0],
  28733. flags: new Uint8Array(data.subarray(1, 4)),
  28734. sampleToChunks: []
  28735. },
  28736. i;
  28737. for (i = 8; entryCount; i += 12, entryCount--) {
  28738. result.sampleToChunks.push({
  28739. firstChunk: view.getUint32(i),
  28740. samplesPerChunk: view.getUint32(i + 4),
  28741. sampleDescriptionIndex: view.getUint32(i + 8)
  28742. });
  28743. }
  28744. return result;
  28745. },
  28746. stsd: function stsd(data) {
  28747. return {
  28748. version: data[0],
  28749. flags: new Uint8Array(data.subarray(1, 4)),
  28750. sampleDescriptions: inspectMp4(data.subarray(8))
  28751. };
  28752. },
  28753. stsz: function stsz(data) {
  28754. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28755. result = {
  28756. version: data[0],
  28757. flags: new Uint8Array(data.subarray(1, 4)),
  28758. sampleSize: view.getUint32(4),
  28759. entries: []
  28760. },
  28761. i;
  28762. for (i = 12; i < data.byteLength; i += 4) {
  28763. result.entries.push(view.getUint32(i));
  28764. }
  28765. return result;
  28766. },
  28767. stts: function stts(data) {
  28768. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28769. result = {
  28770. version: data[0],
  28771. flags: new Uint8Array(data.subarray(1, 4)),
  28772. timeToSamples: []
  28773. },
  28774. entryCount = view.getUint32(4),
  28775. i;
  28776. for (i = 8; entryCount; i += 8, entryCount--) {
  28777. result.timeToSamples.push({
  28778. sampleCount: view.getUint32(i),
  28779. sampleDelta: view.getUint32(i + 4)
  28780. });
  28781. }
  28782. return result;
  28783. },
  28784. styp: function styp(data) {
  28785. return parse$1.ftyp(data);
  28786. },
  28787. tfdt: function tfdt(data) {
  28788. var result = {
  28789. version: data[0],
  28790. flags: new Uint8Array(data.subarray(1, 4)),
  28791. baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  28792. };
  28793. if (result.version === 1) {
  28794. result.baseMediaDecodeTime *= Math.pow(2, 32);
  28795. result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
  28796. }
  28797. return result;
  28798. },
  28799. tfhd: function tfhd(data) {
  28800. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28801. result = {
  28802. version: data[0],
  28803. flags: new Uint8Array(data.subarray(1, 4)),
  28804. trackId: view.getUint32(4)
  28805. },
  28806. baseDataOffsetPresent = result.flags[2] & 0x01,
  28807. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  28808. defaultSampleDurationPresent = result.flags[2] & 0x08,
  28809. defaultSampleSizePresent = result.flags[2] & 0x10,
  28810. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  28811. durationIsEmpty = result.flags[0] & 0x010000,
  28812. defaultBaseIsMoof = result.flags[0] & 0x020000,
  28813. i;
  28814. i = 8;
  28815. if (baseDataOffsetPresent) {
  28816. i += 4; // truncate top 4 bytes
  28817. // FIXME: should we read the full 64 bits?
  28818. result.baseDataOffset = view.getUint32(12);
  28819. i += 4;
  28820. }
  28821. if (sampleDescriptionIndexPresent) {
  28822. result.sampleDescriptionIndex = view.getUint32(i);
  28823. i += 4;
  28824. }
  28825. if (defaultSampleDurationPresent) {
  28826. result.defaultSampleDuration = view.getUint32(i);
  28827. i += 4;
  28828. }
  28829. if (defaultSampleSizePresent) {
  28830. result.defaultSampleSize = view.getUint32(i);
  28831. i += 4;
  28832. }
  28833. if (defaultSampleFlagsPresent) {
  28834. result.defaultSampleFlags = view.getUint32(i);
  28835. }
  28836. if (durationIsEmpty) {
  28837. result.durationIsEmpty = true;
  28838. }
  28839. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  28840. result.baseDataOffsetIsMoof = true;
  28841. }
  28842. return result;
  28843. },
  28844. tkhd: function tkhd(data) {
  28845. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28846. i = 4,
  28847. result = {
  28848. version: view.getUint8(0),
  28849. flags: new Uint8Array(data.subarray(1, 4))
  28850. };
  28851. if (result.version === 1) {
  28852. i += 4;
  28853. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28854. i += 8;
  28855. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28856. i += 4;
  28857. result.trackId = view.getUint32(i);
  28858. i += 4;
  28859. i += 8;
  28860. result.duration = view.getUint32(i); // truncating top 4 bytes
  28861. } else {
  28862. result.creationTime = parseMp4Date(view.getUint32(i));
  28863. i += 4;
  28864. result.modificationTime = parseMp4Date(view.getUint32(i));
  28865. i += 4;
  28866. result.trackId = view.getUint32(i);
  28867. i += 4;
  28868. i += 4;
  28869. result.duration = view.getUint32(i);
  28870. }
  28871. i += 4;
  28872. i += 2 * 4;
  28873. result.layer = view.getUint16(i);
  28874. i += 2;
  28875. result.alternateGroup = view.getUint16(i);
  28876. i += 2; // convert fixed-point, base 16 back to a number
  28877. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  28878. i += 2;
  28879. i += 2;
  28880. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  28881. i += 9 * 4;
  28882. result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
  28883. i += 4;
  28884. result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
  28885. return result;
  28886. },
  28887. traf: function traf(data) {
  28888. return {
  28889. boxes: inspectMp4(data)
  28890. };
  28891. },
  28892. trak: function trak(data) {
  28893. return {
  28894. boxes: inspectMp4(data)
  28895. };
  28896. },
  28897. trex: function trex(data) {
  28898. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28899. return {
  28900. version: data[0],
  28901. flags: new Uint8Array(data.subarray(1, 4)),
  28902. trackId: view.getUint32(4),
  28903. defaultSampleDescriptionIndex: view.getUint32(8),
  28904. defaultSampleDuration: view.getUint32(12),
  28905. defaultSampleSize: view.getUint32(16),
  28906. sampleDependsOn: data[20] & 0x03,
  28907. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  28908. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  28909. samplePaddingValue: (data[21] & 0x0e) >> 1,
  28910. sampleIsDifferenceSample: !!(data[21] & 0x01),
  28911. sampleDegradationPriority: view.getUint16(22)
  28912. };
  28913. },
  28914. trun: function trun(data) {
  28915. var result = {
  28916. version: data[0],
  28917. flags: new Uint8Array(data.subarray(1, 4)),
  28918. samples: []
  28919. },
  28920. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28921. // Flag interpretation
  28922. dataOffsetPresent = result.flags[2] & 0x01,
  28923. // compare with 2nd byte of 0x1
  28924. firstSampleFlagsPresent = result.flags[2] & 0x04,
  28925. // compare with 2nd byte of 0x4
  28926. sampleDurationPresent = result.flags[1] & 0x01,
  28927. // compare with 2nd byte of 0x100
  28928. sampleSizePresent = result.flags[1] & 0x02,
  28929. // compare with 2nd byte of 0x200
  28930. sampleFlagsPresent = result.flags[1] & 0x04,
  28931. // compare with 2nd byte of 0x400
  28932. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  28933. // compare with 2nd byte of 0x800
  28934. sampleCount = view.getUint32(4),
  28935. offset = 8,
  28936. sample;
  28937. if (dataOffsetPresent) {
  28938. // 32 bit signed integer
  28939. result.dataOffset = view.getInt32(offset);
  28940. offset += 4;
  28941. } // Overrides the flags for the first sample only. The order of
  28942. // optional values will be: duration, size, compositionTimeOffset
  28943. if (firstSampleFlagsPresent && sampleCount) {
  28944. sample = {
  28945. flags: parseSampleFlags(data.subarray(offset, offset + 4))
  28946. };
  28947. offset += 4;
  28948. if (sampleDurationPresent) {
  28949. sample.duration = view.getUint32(offset);
  28950. offset += 4;
  28951. }
  28952. if (sampleSizePresent) {
  28953. sample.size = view.getUint32(offset);
  28954. offset += 4;
  28955. }
  28956. if (sampleCompositionTimeOffsetPresent) {
  28957. // Note: this should be a signed int if version is 1
  28958. sample.compositionTimeOffset = view.getUint32(offset);
  28959. offset += 4;
  28960. }
  28961. result.samples.push(sample);
  28962. sampleCount--;
  28963. }
  28964. while (sampleCount--) {
  28965. sample = {};
  28966. if (sampleDurationPresent) {
  28967. sample.duration = view.getUint32(offset);
  28968. offset += 4;
  28969. }
  28970. if (sampleSizePresent) {
  28971. sample.size = view.getUint32(offset);
  28972. offset += 4;
  28973. }
  28974. if (sampleFlagsPresent) {
  28975. sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
  28976. offset += 4;
  28977. }
  28978. if (sampleCompositionTimeOffsetPresent) {
  28979. // Note: this should be a signed int if version is 1
  28980. sample.compositionTimeOffset = view.getUint32(offset);
  28981. offset += 4;
  28982. }
  28983. result.samples.push(sample);
  28984. }
  28985. return result;
  28986. },
  28987. 'url ': function url(data) {
  28988. return {
  28989. version: data[0],
  28990. flags: new Uint8Array(data.subarray(1, 4))
  28991. };
  28992. },
  28993. vmhd: function vmhd(data) {
  28994. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28995. return {
  28996. version: data[0],
  28997. flags: new Uint8Array(data.subarray(1, 4)),
  28998. graphicsmode: view.getUint16(4),
  28999. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  29000. };
  29001. }
  29002. };
  29003. /**
  29004. * Return a javascript array of box objects parsed from an ISO base
  29005. * media file.
  29006. * @param data {Uint8Array} the binary data of the media to be inspected
  29007. * @return {array} a javascript array of potentially nested box objects
  29008. */
  29009. inspectMp4 = function inspectMp4(data) {
  29010. var i = 0,
  29011. result = [],
  29012. view,
  29013. size,
  29014. type,
  29015. end,
  29016. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  29017. var ab = new ArrayBuffer(data.length);
  29018. var v = new Uint8Array(ab);
  29019. for (var z = 0; z < data.length; ++z) {
  29020. v[z] = data[z];
  29021. }
  29022. view = new DataView(ab);
  29023. while (i < data.byteLength) {
  29024. // parse box data
  29025. size = view.getUint32(i);
  29026. type = parseType$1(data.subarray(i + 4, i + 8));
  29027. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  29028. box = (parse$1[type] || function (data) {
  29029. return {
  29030. data: data
  29031. };
  29032. })(data.subarray(i + 8, end));
  29033. box.size = size;
  29034. box.type = type; // store this box and move to the next
  29035. result.push(box);
  29036. i = end;
  29037. }
  29038. return result;
  29039. };
  29040. /**
  29041. * Returns a textual representation of the javascript represtentation
  29042. * of an MP4 file. You can use it as an alternative to
  29043. * JSON.stringify() to compare inspected MP4s.
  29044. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  29045. * file
  29046. * @param depth {number} (optional) the number of ancestor boxes of
  29047. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  29048. * @return {string} a text representation of the parsed MP4
  29049. */
  29050. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  29051. var indent;
  29052. depth = depth || 0;
  29053. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  29054. return inspectedMp4.map(function (box, index) {
  29055. // list the box type first at the current indentation level
  29056. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  29057. Object.keys(box).filter(function (key) {
  29058. return key !== 'type' && key !== 'boxes'; // output all the box properties
  29059. }).map(function (key) {
  29060. var prefix = indent + ' ' + key + ': ',
  29061. value = box[key]; // print out raw bytes as hexademical
  29062. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  29063. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (_byte) {
  29064. return ' ' + ('00' + _byte.toString(16)).slice(-2);
  29065. }).join('').match(/.{1,24}/g);
  29066. if (!bytes) {
  29067. return prefix + '<>';
  29068. }
  29069. if (bytes.length === 1) {
  29070. return prefix + '<' + bytes.join('').slice(1) + '>';
  29071. }
  29072. return prefix + '<\n' + bytes.map(function (line) {
  29073. return indent + ' ' + line;
  29074. }).join('\n') + '\n' + indent + ' >';
  29075. } // stringify generic objects
  29076. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  29077. if (index === 0) {
  29078. return line;
  29079. }
  29080. return indent + ' ' + line;
  29081. }).join('\n');
  29082. }).join('\n') + ( // recursively textify the child boxes
  29083. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  29084. }).join('\n');
  29085. };
  29086. var mp4Inspector = {
  29087. inspect: inspectMp4,
  29088. textify: _textifyMp,
  29089. parseTfdt: parse$1.tfdt,
  29090. parseHdlr: parse$1.hdlr,
  29091. parseTfhd: parse$1.tfhd,
  29092. parseTrun: parse$1.trun,
  29093. parseSidx: parse$1.sidx
  29094. };
  29095. /**
  29096. * mux.js
  29097. *
  29098. * Copyright (c) Brightcove
  29099. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  29100. *
  29101. * Functions that generate fragmented MP4s suitable for use with Media
  29102. * Source Extensions.
  29103. */
  29104. var UINT32_MAX = Math.pow(2, 32) - 1;
  29105. 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
  29106. (function () {
  29107. var i;
  29108. types = {
  29109. avc1: [],
  29110. // codingname
  29111. avcC: [],
  29112. btrt: [],
  29113. dinf: [],
  29114. dref: [],
  29115. esds: [],
  29116. ftyp: [],
  29117. hdlr: [],
  29118. mdat: [],
  29119. mdhd: [],
  29120. mdia: [],
  29121. mfhd: [],
  29122. minf: [],
  29123. moof: [],
  29124. moov: [],
  29125. mp4a: [],
  29126. // codingname
  29127. mvex: [],
  29128. mvhd: [],
  29129. sdtp: [],
  29130. smhd: [],
  29131. stbl: [],
  29132. stco: [],
  29133. stsc: [],
  29134. stsd: [],
  29135. stsz: [],
  29136. stts: [],
  29137. styp: [],
  29138. tfdt: [],
  29139. tfhd: [],
  29140. traf: [],
  29141. trak: [],
  29142. trun: [],
  29143. trex: [],
  29144. tkhd: [],
  29145. vmhd: []
  29146. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  29147. // don't throw an error
  29148. if (typeof Uint8Array === 'undefined') {
  29149. return;
  29150. }
  29151. for (i in types) {
  29152. if (types.hasOwnProperty(i)) {
  29153. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  29154. }
  29155. }
  29156. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  29157. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  29158. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  29159. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  29160. 0x00, 0x00, 0x00, // flags
  29161. 0x00, 0x00, 0x00, 0x00, // pre_defined
  29162. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  29163. 0x00, 0x00, 0x00, 0x00, // reserved
  29164. 0x00, 0x00, 0x00, 0x00, // reserved
  29165. 0x00, 0x00, 0x00, 0x00, // reserved
  29166. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  29167. ]);
  29168. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  29169. 0x00, 0x00, 0x00, // flags
  29170. 0x00, 0x00, 0x00, 0x00, // pre_defined
  29171. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  29172. 0x00, 0x00, 0x00, 0x00, // reserved
  29173. 0x00, 0x00, 0x00, 0x00, // reserved
  29174. 0x00, 0x00, 0x00, 0x00, // reserved
  29175. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  29176. ]);
  29177. HDLR_TYPES = {
  29178. video: VIDEO_HDLR,
  29179. audio: AUDIO_HDLR
  29180. };
  29181. DREF = new Uint8Array([0x00, // version 0
  29182. 0x00, 0x00, 0x00, // flags
  29183. 0x00, 0x00, 0x00, 0x01, // entry_count
  29184. 0x00, 0x00, 0x00, 0x0c, // entry_size
  29185. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  29186. 0x00, // version 0
  29187. 0x00, 0x00, 0x01 // entry_flags
  29188. ]);
  29189. SMHD = new Uint8Array([0x00, // version
  29190. 0x00, 0x00, 0x00, // flags
  29191. 0x00, 0x00, // balance, 0 means centered
  29192. 0x00, 0x00 // reserved
  29193. ]);
  29194. STCO = new Uint8Array([0x00, // version
  29195. 0x00, 0x00, 0x00, // flags
  29196. 0x00, 0x00, 0x00, 0x00 // entry_count
  29197. ]);
  29198. STSC = STCO;
  29199. STSZ = new Uint8Array([0x00, // version
  29200. 0x00, 0x00, 0x00, // flags
  29201. 0x00, 0x00, 0x00, 0x00, // sample_size
  29202. 0x00, 0x00, 0x00, 0x00 // sample_count
  29203. ]);
  29204. STTS = STCO;
  29205. VMHD = new Uint8Array([0x00, // version
  29206. 0x00, 0x00, 0x01, // flags
  29207. 0x00, 0x00, // graphicsmode
  29208. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  29209. ]);
  29210. })();
  29211. box = function box(type) {
  29212. var payload = [],
  29213. size = 0,
  29214. i,
  29215. result,
  29216. view;
  29217. for (i = 1; i < arguments.length; i++) {
  29218. payload.push(arguments[i]);
  29219. }
  29220. i = payload.length; // calculate the total size we need to allocate
  29221. while (i--) {
  29222. size += payload[i].byteLength;
  29223. }
  29224. result = new Uint8Array(size + 8);
  29225. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  29226. view.setUint32(0, result.byteLength);
  29227. result.set(type, 4); // copy the payload into the result
  29228. for (i = 0, size = 8; i < payload.length; i++) {
  29229. result.set(payload[i], size);
  29230. size += payload[i].byteLength;
  29231. }
  29232. return result;
  29233. };
  29234. dinf = function dinf() {
  29235. return box(types.dinf, box(types.dref, DREF));
  29236. };
  29237. esds = function esds(track) {
  29238. return box(types.esds, new Uint8Array([0x00, // version
  29239. 0x00, 0x00, 0x00, // flags
  29240. // ES_Descriptor
  29241. 0x03, // tag, ES_DescrTag
  29242. 0x19, // length
  29243. 0x00, 0x00, // ES_ID
  29244. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  29245. // DecoderConfigDescriptor
  29246. 0x04, // tag, DecoderConfigDescrTag
  29247. 0x11, // length
  29248. 0x40, // object type
  29249. 0x15, // streamType
  29250. 0x00, 0x06, 0x00, // bufferSizeDB
  29251. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  29252. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  29253. // DecoderSpecificInfo
  29254. 0x05, // tag, DecoderSpecificInfoTag
  29255. 0x02, // length
  29256. // ISO/IEC 14496-3, AudioSpecificConfig
  29257. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  29258. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  29259. ]));
  29260. };
  29261. ftyp = function ftyp() {
  29262. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  29263. };
  29264. hdlr = function hdlr(type) {
  29265. return box(types.hdlr, HDLR_TYPES[type]);
  29266. };
  29267. mdat = function mdat(data) {
  29268. return box(types.mdat, data);
  29269. };
  29270. mdhd = function mdhd(track) {
  29271. var result = new Uint8Array([0x00, // version 0
  29272. 0x00, 0x00, 0x00, // flags
  29273. 0x00, 0x00, 0x00, 0x02, // creation_time
  29274. 0x00, 0x00, 0x00, 0x03, // modification_time
  29275. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  29276. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  29277. 0x55, 0xc4, // 'und' language (undetermined)
  29278. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  29279. // defined. The sample rate can be parsed out of an ADTS header, for
  29280. // instance.
  29281. if (track.samplerate) {
  29282. result[12] = track.samplerate >>> 24 & 0xFF;
  29283. result[13] = track.samplerate >>> 16 & 0xFF;
  29284. result[14] = track.samplerate >>> 8 & 0xFF;
  29285. result[15] = track.samplerate & 0xFF;
  29286. }
  29287. return box(types.mdhd, result);
  29288. };
  29289. mdia = function mdia(track) {
  29290. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  29291. };
  29292. mfhd = function mfhd(sequenceNumber) {
  29293. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  29294. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  29295. ]));
  29296. };
  29297. minf = function minf(track) {
  29298. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  29299. };
  29300. moof = function moof(sequenceNumber, tracks) {
  29301. var trackFragments = [],
  29302. i = tracks.length; // build traf boxes for each track fragment
  29303. while (i--) {
  29304. trackFragments[i] = traf(tracks[i]);
  29305. }
  29306. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  29307. };
  29308. /**
  29309. * Returns a movie box.
  29310. * @param tracks {array} the tracks associated with this movie
  29311. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  29312. */
  29313. moov = function moov(tracks) {
  29314. var i = tracks.length,
  29315. boxes = [];
  29316. while (i--) {
  29317. boxes[i] = trak(tracks[i]);
  29318. }
  29319. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  29320. };
  29321. mvex = function mvex(tracks) {
  29322. var i = tracks.length,
  29323. boxes = [];
  29324. while (i--) {
  29325. boxes[i] = trex(tracks[i]);
  29326. }
  29327. return box.apply(null, [types.mvex].concat(boxes));
  29328. };
  29329. mvhd = function mvhd(duration) {
  29330. var bytes = new Uint8Array([0x00, // version 0
  29331. 0x00, 0x00, 0x00, // flags
  29332. 0x00, 0x00, 0x00, 0x01, // creation_time
  29333. 0x00, 0x00, 0x00, 0x02, // modification_time
  29334. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  29335. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  29336. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  29337. 0x01, 0x00, // 1.0 volume
  29338. 0x00, 0x00, // reserved
  29339. 0x00, 0x00, 0x00, 0x00, // reserved
  29340. 0x00, 0x00, 0x00, 0x00, // reserved
  29341. 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
  29342. 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
  29343. 0xff, 0xff, 0xff, 0xff // next_track_ID
  29344. ]);
  29345. return box(types.mvhd, bytes);
  29346. };
  29347. sdtp = function sdtp(track) {
  29348. var samples = track.samples || [],
  29349. bytes = new Uint8Array(4 + samples.length),
  29350. flags,
  29351. i; // leave the full box header (4 bytes) all zero
  29352. // write the sample table
  29353. for (i = 0; i < samples.length; i++) {
  29354. flags = samples[i].flags;
  29355. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  29356. }
  29357. return box(types.sdtp, bytes);
  29358. };
  29359. stbl = function stbl(track) {
  29360. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  29361. };
  29362. (function () {
  29363. var videoSample, audioSample;
  29364. stsd = function stsd(track) {
  29365. return box(types.stsd, new Uint8Array([0x00, // version 0
  29366. 0x00, 0x00, 0x00, // flags
  29367. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  29368. };
  29369. videoSample = function videoSample(track) {
  29370. var sps = track.sps || [],
  29371. pps = track.pps || [],
  29372. sequenceParameterSets = [],
  29373. pictureParameterSets = [],
  29374. i; // assemble the SPSs
  29375. for (i = 0; i < sps.length; i++) {
  29376. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  29377. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  29378. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  29379. } // assemble the PPSs
  29380. for (i = 0; i < pps.length; i++) {
  29381. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  29382. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  29383. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  29384. }
  29385. return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  29386. 0x00, 0x01, // data_reference_index
  29387. 0x00, 0x00, // pre_defined
  29388. 0x00, 0x00, // reserved
  29389. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  29390. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  29391. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  29392. 0x00, 0x48, 0x00, 0x00, // horizresolution
  29393. 0x00, 0x48, 0x00, 0x00, // vertresolution
  29394. 0x00, 0x00, 0x00, 0x00, // reserved
  29395. 0x00, 0x01, // frame_count
  29396. 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
  29397. 0x00, 0x18, // depth = 24
  29398. 0x11, 0x11 // pre_defined = -1
  29399. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  29400. track.profileIdc, // AVCProfileIndication
  29401. track.profileCompatibility, // profile_compatibility
  29402. track.levelIdc, // AVCLevelIndication
  29403. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  29404. ].concat([sps.length // numOfSequenceParameterSets
  29405. ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
  29406. ]).concat(pictureParameterSets))), // "PPS"
  29407. box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  29408. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  29409. 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
  29410. );
  29411. };
  29412. audioSample = function audioSample(track) {
  29413. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  29414. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  29415. 0x00, 0x01, // data_reference_index
  29416. // AudioSampleEntry, ISO/IEC 14496-12
  29417. 0x00, 0x00, 0x00, 0x00, // reserved
  29418. 0x00, 0x00, 0x00, 0x00, // reserved
  29419. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  29420. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  29421. 0x00, 0x00, // pre_defined
  29422. 0x00, 0x00, // reserved
  29423. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  29424. // MP4AudioSampleEntry, ISO/IEC 14496-14
  29425. ]), esds(track));
  29426. };
  29427. })();
  29428. tkhd = function tkhd(track) {
  29429. var result = new Uint8Array([0x00, // version 0
  29430. 0x00, 0x00, 0x07, // flags
  29431. 0x00, 0x00, 0x00, 0x00, // creation_time
  29432. 0x00, 0x00, 0x00, 0x00, // modification_time
  29433. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  29434. 0x00, 0x00, 0x00, 0x00, // reserved
  29435. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  29436. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  29437. 0x00, 0x00, // layer
  29438. 0x00, 0x00, // alternate_group
  29439. 0x01, 0x00, // non-audio track volume
  29440. 0x00, 0x00, // reserved
  29441. 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
  29442. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  29443. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  29444. ]);
  29445. return box(types.tkhd, result);
  29446. };
  29447. /**
  29448. * Generate a track fragment (traf) box. A traf box collects metadata
  29449. * about tracks in a movie fragment (moof) box.
  29450. */
  29451. traf = function traf(track) {
  29452. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  29453. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  29454. 0x00, 0x00, 0x3a, // flags
  29455. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  29456. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  29457. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  29458. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  29459. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  29460. ]));
  29461. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
  29462. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
  29463. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  29464. 0x00, 0x00, 0x00, // flags
  29465. // baseMediaDecodeTime
  29466. 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
  29467. // the containing moof to the first payload byte of the associated
  29468. // mdat
  29469. dataOffset = 32 + // tfhd
  29470. 20 + // tfdt
  29471. 8 + // traf header
  29472. 16 + // mfhd
  29473. 8 + // moof header
  29474. 8; // mdat header
  29475. // audio tracks require less metadata
  29476. if (track.type === 'audio') {
  29477. trackFragmentRun = trun(track, dataOffset);
  29478. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  29479. } // video tracks should contain an independent and disposable samples
  29480. // box (sdtp)
  29481. // generate one and adjust offsets to match
  29482. sampleDependencyTable = sdtp(track);
  29483. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  29484. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  29485. };
  29486. /**
  29487. * Generate a track box.
  29488. * @param track {object} a track definition
  29489. * @return {Uint8Array} the track box
  29490. */
  29491. trak = function trak(track) {
  29492. track.duration = track.duration || 0xffffffff;
  29493. return box(types.trak, tkhd(track), mdia(track));
  29494. };
  29495. trex = function trex(track) {
  29496. var result = new Uint8Array([0x00, // version 0
  29497. 0x00, 0x00, 0x00, // flags
  29498. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  29499. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  29500. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  29501. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  29502. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  29503. ]); // the last two bytes of default_sample_flags is the sample
  29504. // degradation priority, a hint about the importance of this sample
  29505. // relative to others. Lower the degradation priority for all sample
  29506. // types other than video.
  29507. if (track.type !== 'video') {
  29508. result[result.length - 1] = 0x00;
  29509. }
  29510. return box(types.trex, result);
  29511. };
  29512. (function () {
  29513. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  29514. // duration is present for the first sample, it will be present for
  29515. // all subsequent samples.
  29516. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  29517. trunHeader = function trunHeader(samples, offset) {
  29518. var durationPresent = 0,
  29519. sizePresent = 0,
  29520. flagsPresent = 0,
  29521. compositionTimeOffset = 0; // trun flag constants
  29522. if (samples.length) {
  29523. if (samples[0].duration !== undefined) {
  29524. durationPresent = 0x1;
  29525. }
  29526. if (samples[0].size !== undefined) {
  29527. sizePresent = 0x2;
  29528. }
  29529. if (samples[0].flags !== undefined) {
  29530. flagsPresent = 0x4;
  29531. }
  29532. if (samples[0].compositionTimeOffset !== undefined) {
  29533. compositionTimeOffset = 0x8;
  29534. }
  29535. }
  29536. return [0x00, // version 0
  29537. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  29538. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  29539. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  29540. ];
  29541. };
  29542. videoTrun = function videoTrun(track, offset) {
  29543. var bytes, samples, sample, i;
  29544. samples = track.samples || [];
  29545. offset += 8 + 12 + 16 * samples.length;
  29546. bytes = trunHeader(samples, offset);
  29547. for (i = 0; i < samples.length; i++) {
  29548. sample = samples[i];
  29549. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  29550. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
  29551. 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
  29552. (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
  29553. ]);
  29554. }
  29555. return box(types.trun, new Uint8Array(bytes));
  29556. };
  29557. audioTrun = function audioTrun(track, offset) {
  29558. var bytes, samples, sample, i;
  29559. samples = track.samples || [];
  29560. offset += 8 + 12 + 8 * samples.length;
  29561. bytes = trunHeader(samples, offset);
  29562. for (i = 0; i < samples.length; i++) {
  29563. sample = samples[i];
  29564. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  29565. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
  29566. }
  29567. return box(types.trun, new Uint8Array(bytes));
  29568. };
  29569. trun = function trun(track, offset) {
  29570. if (track.type === 'audio') {
  29571. return audioTrun(track, offset);
  29572. }
  29573. return videoTrun(track, offset);
  29574. };
  29575. })();
  29576. var mp4Generator = {
  29577. ftyp: ftyp,
  29578. mdat: mdat,
  29579. moof: moof,
  29580. moov: moov,
  29581. initSegment: function initSegment(tracks) {
  29582. var fileType = ftyp(),
  29583. movie = moov(tracks),
  29584. result;
  29585. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  29586. result.set(fileType);
  29587. result.set(movie, fileType.byteLength);
  29588. return result;
  29589. }
  29590. };
  29591. /**
  29592. * mux.js
  29593. *
  29594. * Copyright (c) Brightcove
  29595. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  29596. *
  29597. * A lightweight readable stream implemention that handles event dispatching.
  29598. * Objects that inherit from streams should call init in their constructors.
  29599. */
  29600. var Stream$1 = function Stream() {
  29601. this.init = function () {
  29602. var listeners = {};
  29603. /**
  29604. * Add a listener for a specified event type.
  29605. * @param type {string} the event name
  29606. * @param listener {function} the callback to be invoked when an event of
  29607. * the specified type occurs
  29608. */
  29609. this.on = function (type, listener) {
  29610. if (!listeners[type]) {
  29611. listeners[type] = [];
  29612. }
  29613. listeners[type] = listeners[type].concat(listener);
  29614. };
  29615. /**
  29616. * Remove a listener for a specified event type.
  29617. * @param type {string} the event name
  29618. * @param listener {function} a function previously registered for this
  29619. * type of event through `on`
  29620. */
  29621. this.off = function (type, listener) {
  29622. var index;
  29623. if (!listeners[type]) {
  29624. return false;
  29625. }
  29626. index = listeners[type].indexOf(listener);
  29627. listeners[type] = listeners[type].slice();
  29628. listeners[type].splice(index, 1);
  29629. return index > -1;
  29630. };
  29631. /**
  29632. * Trigger an event of the specified type on this stream. Any additional
  29633. * arguments to this function are passed as parameters to event listeners.
  29634. * @param type {string} the event name
  29635. */
  29636. this.trigger = function (type) {
  29637. var callbacks, i, length, args;
  29638. callbacks = listeners[type];
  29639. if (!callbacks) {
  29640. return;
  29641. } // Slicing the arguments on every invocation of this method
  29642. // can add a significant amount of overhead. Avoid the
  29643. // intermediate object creation for the common case of a
  29644. // single callback argument
  29645. if (arguments.length === 2) {
  29646. length = callbacks.length;
  29647. for (i = 0; i < length; ++i) {
  29648. callbacks[i].call(this, arguments[1]);
  29649. }
  29650. } else {
  29651. args = [];
  29652. i = arguments.length;
  29653. for (i = 1; i < arguments.length; ++i) {
  29654. args.push(arguments[i]);
  29655. }
  29656. length = callbacks.length;
  29657. for (i = 0; i < length; ++i) {
  29658. callbacks[i].apply(this, args);
  29659. }
  29660. }
  29661. };
  29662. /**
  29663. * Destroys the stream and cleans up.
  29664. */
  29665. this.dispose = function () {
  29666. listeners = {};
  29667. };
  29668. };
  29669. };
  29670. /**
  29671. * Forwards all `data` events on this stream to the destination stream. The
  29672. * destination stream should provide a method `push` to receive the data
  29673. * events as they arrive.
  29674. * @param destination {stream} the stream that will receive all `data` events
  29675. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  29676. * when the current stream emits a 'done' event
  29677. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  29678. */
  29679. Stream$1.prototype.pipe = function (destination) {
  29680. this.on('data', function (data) {
  29681. destination.push(data);
  29682. });
  29683. this.on('done', function (flushSource) {
  29684. destination.flush(flushSource);
  29685. });
  29686. return destination;
  29687. }; // Default stream functions that are expected to be overridden to perform
  29688. // actual work. These are provided by the prototype as a sort of no-op
  29689. // implementation so that we don't have to check for their existence in the
  29690. // `pipe` function above.
  29691. Stream$1.prototype.push = function (data) {
  29692. this.trigger('data', data);
  29693. };
  29694. Stream$1.prototype.flush = function (flushSource) {
  29695. this.trigger('done', flushSource);
  29696. };
  29697. var stream = Stream$1;
  29698. /**
  29699. * mux.js
  29700. *
  29701. * Copyright (c) Brightcove
  29702. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  29703. */
  29704. // Convert an array of nal units into an array of frames with each frame being
  29705. // composed of the nal units that make up that frame
  29706. // Also keep track of cummulative data about the frame from the nal units such
  29707. // as the frame duration, starting pts, etc.
  29708. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  29709. var i,
  29710. currentNal,
  29711. currentFrame = [],
  29712. frames = [];
  29713. currentFrame.byteLength = 0;
  29714. for (i = 0; i < nalUnits.length; i++) {
  29715. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  29716. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  29717. // Since the very first nal unit is expected to be an AUD
  29718. // only push to the frames array when currentFrame is not empty
  29719. if (currentFrame.length) {
  29720. currentFrame.duration = currentNal.dts - currentFrame.dts;
  29721. frames.push(currentFrame);
  29722. }
  29723. currentFrame = [currentNal];
  29724. currentFrame.byteLength = currentNal.data.byteLength;
  29725. currentFrame.pts = currentNal.pts;
  29726. currentFrame.dts = currentNal.dts;
  29727. } else {
  29728. // Specifically flag key frames for ease of use later
  29729. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  29730. currentFrame.keyFrame = true;
  29731. }
  29732. currentFrame.duration = currentNal.dts - currentFrame.dts;
  29733. currentFrame.byteLength += currentNal.data.byteLength;
  29734. currentFrame.push(currentNal);
  29735. }
  29736. } // For the last frame, use the duration of the previous frame if we
  29737. // have nothing better to go on
  29738. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  29739. currentFrame.duration = frames[frames.length - 1].duration;
  29740. } // Push the final frame
  29741. frames.push(currentFrame);
  29742. return frames;
  29743. }; // Convert an array of frames into an array of Gop with each Gop being composed
  29744. // of the frames that make up that Gop
  29745. // Also keep track of cummulative data about the Gop from the frames such as the
  29746. // Gop duration, starting pts, etc.
  29747. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  29748. var i,
  29749. currentFrame,
  29750. currentGop = [],
  29751. gops = []; // We must pre-set some of the values on the Gop since we
  29752. // keep running totals of these values
  29753. currentGop.byteLength = 0;
  29754. currentGop.nalCount = 0;
  29755. currentGop.duration = 0;
  29756. currentGop.pts = frames[0].pts;
  29757. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  29758. gops.byteLength = 0;
  29759. gops.nalCount = 0;
  29760. gops.duration = 0;
  29761. gops.pts = frames[0].pts;
  29762. gops.dts = frames[0].dts;
  29763. for (i = 0; i < frames.length; i++) {
  29764. currentFrame = frames[i];
  29765. if (currentFrame.keyFrame) {
  29766. // Since the very first frame is expected to be an keyframe
  29767. // only push to the gops array when currentGop is not empty
  29768. if (currentGop.length) {
  29769. gops.push(currentGop);
  29770. gops.byteLength += currentGop.byteLength;
  29771. gops.nalCount += currentGop.nalCount;
  29772. gops.duration += currentGop.duration;
  29773. }
  29774. currentGop = [currentFrame];
  29775. currentGop.nalCount = currentFrame.length;
  29776. currentGop.byteLength = currentFrame.byteLength;
  29777. currentGop.pts = currentFrame.pts;
  29778. currentGop.dts = currentFrame.dts;
  29779. currentGop.duration = currentFrame.duration;
  29780. } else {
  29781. currentGop.duration += currentFrame.duration;
  29782. currentGop.nalCount += currentFrame.length;
  29783. currentGop.byteLength += currentFrame.byteLength;
  29784. currentGop.push(currentFrame);
  29785. }
  29786. }
  29787. if (gops.length && currentGop.duration <= 0) {
  29788. currentGop.duration = gops[gops.length - 1].duration;
  29789. }
  29790. gops.byteLength += currentGop.byteLength;
  29791. gops.nalCount += currentGop.nalCount;
  29792. gops.duration += currentGop.duration; // push the final Gop
  29793. gops.push(currentGop);
  29794. return gops;
  29795. };
  29796. /*
  29797. * Search for the first keyframe in the GOPs and throw away all frames
  29798. * until that keyframe. Then extend the duration of the pulled keyframe
  29799. * and pull the PTS and DTS of the keyframe so that it covers the time
  29800. * range of the frames that were disposed.
  29801. *
  29802. * @param {Array} gops video GOPs
  29803. * @returns {Array} modified video GOPs
  29804. */
  29805. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  29806. var currentGop;
  29807. if (!gops[0][0].keyFrame && gops.length > 1) {
  29808. // Remove the first GOP
  29809. currentGop = gops.shift();
  29810. gops.byteLength -= currentGop.byteLength;
  29811. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  29812. // first gop to cover the time period of the
  29813. // frames we just removed
  29814. gops[0][0].dts = currentGop.dts;
  29815. gops[0][0].pts = currentGop.pts;
  29816. gops[0][0].duration += currentGop.duration;
  29817. }
  29818. return gops;
  29819. };
  29820. /**
  29821. * Default sample object
  29822. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  29823. */
  29824. var createDefaultSample = function createDefaultSample() {
  29825. return {
  29826. size: 0,
  29827. flags: {
  29828. isLeading: 0,
  29829. dependsOn: 1,
  29830. isDependedOn: 0,
  29831. hasRedundancy: 0,
  29832. degradationPriority: 0,
  29833. isNonSyncSample: 1
  29834. }
  29835. };
  29836. };
  29837. /*
  29838. * Collates information from a video frame into an object for eventual
  29839. * entry into an MP4 sample table.
  29840. *
  29841. * @param {Object} frame the video frame
  29842. * @param {Number} dataOffset the byte offset to position the sample
  29843. * @return {Object} object containing sample table info for a frame
  29844. */
  29845. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  29846. var sample = createDefaultSample();
  29847. sample.dataOffset = dataOffset;
  29848. sample.compositionTimeOffset = frame.pts - frame.dts;
  29849. sample.duration = frame.duration;
  29850. sample.size = 4 * frame.length; // Space for nal unit size
  29851. sample.size += frame.byteLength;
  29852. if (frame.keyFrame) {
  29853. sample.flags.dependsOn = 2;
  29854. sample.flags.isNonSyncSample = 0;
  29855. }
  29856. return sample;
  29857. }; // generate the track's sample table from an array of gops
  29858. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  29859. var h,
  29860. i,
  29861. sample,
  29862. currentGop,
  29863. currentFrame,
  29864. dataOffset = baseDataOffset || 0,
  29865. samples = [];
  29866. for (h = 0; h < gops.length; h++) {
  29867. currentGop = gops[h];
  29868. for (i = 0; i < currentGop.length; i++) {
  29869. currentFrame = currentGop[i];
  29870. sample = sampleForFrame(currentFrame, dataOffset);
  29871. dataOffset += sample.size;
  29872. samples.push(sample);
  29873. }
  29874. }
  29875. return samples;
  29876. }; // generate the track's raw mdat data from an array of gops
  29877. var concatenateNalData = function concatenateNalData(gops) {
  29878. var h,
  29879. i,
  29880. j,
  29881. currentGop,
  29882. currentFrame,
  29883. currentNal,
  29884. dataOffset = 0,
  29885. nalsByteLength = gops.byteLength,
  29886. numberOfNals = gops.nalCount,
  29887. totalByteLength = nalsByteLength + 4 * numberOfNals,
  29888. data = new Uint8Array(totalByteLength),
  29889. view = new DataView(data.buffer); // For each Gop..
  29890. for (h = 0; h < gops.length; h++) {
  29891. currentGop = gops[h]; // For each Frame..
  29892. for (i = 0; i < currentGop.length; i++) {
  29893. currentFrame = currentGop[i]; // For each NAL..
  29894. for (j = 0; j < currentFrame.length; j++) {
  29895. currentNal = currentFrame[j];
  29896. view.setUint32(dataOffset, currentNal.data.byteLength);
  29897. dataOffset += 4;
  29898. data.set(currentNal.data, dataOffset);
  29899. dataOffset += currentNal.data.byteLength;
  29900. }
  29901. }
  29902. }
  29903. return data;
  29904. };
  29905. var frameUtils = {
  29906. groupNalsIntoFrames: groupNalsIntoFrames,
  29907. groupFramesIntoGops: groupFramesIntoGops,
  29908. extendFirstKeyFrame: extendFirstKeyFrame,
  29909. generateSampleTable: generateSampleTable,
  29910. concatenateNalData: concatenateNalData
  29911. };
  29912. /**
  29913. * mux.js
  29914. *
  29915. * Copyright (c) Brightcove
  29916. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  29917. */
  29918. var highPrefix = [33, 16, 5, 32, 164, 27];
  29919. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  29920. var zeroFill = function zeroFill(count) {
  29921. var a = [];
  29922. while (count--) {
  29923. a.push(0);
  29924. }
  29925. return a;
  29926. };
  29927. var makeTable = function makeTable(metaTable) {
  29928. return Object.keys(metaTable).reduce(function (obj, key) {
  29929. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  29930. return arr.concat(part);
  29931. }, []));
  29932. return obj;
  29933. }, {});
  29934. }; // Frames-of-silence to use for filling in missing AAC frames
  29935. var coneOfSilence = {
  29936. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  29937. 88200: [highPrefix, [231], zeroFill(170), [56]],
  29938. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  29939. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  29940. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  29941. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  29942. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  29943. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  29944. 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]],
  29945. 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]],
  29946. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  29947. };
  29948. var silence = makeTable(coneOfSilence);
  29949. /**
  29950. * mux.js
  29951. *
  29952. * Copyright (c) Brightcove
  29953. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  29954. */
  29955. var ONE_SECOND_IN_TS = 90000,
  29956. // 90kHz clock
  29957. secondsToVideoTs,
  29958. secondsToAudioTs,
  29959. videoTsToSeconds,
  29960. audioTsToSeconds,
  29961. audioTsToVideoTs,
  29962. videoTsToAudioTs;
  29963. secondsToVideoTs = function secondsToVideoTs(seconds) {
  29964. return seconds * ONE_SECOND_IN_TS;
  29965. };
  29966. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  29967. return seconds * sampleRate;
  29968. };
  29969. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  29970. return timestamp / ONE_SECOND_IN_TS;
  29971. };
  29972. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  29973. return timestamp / sampleRate;
  29974. };
  29975. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  29976. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  29977. };
  29978. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  29979. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  29980. };
  29981. var clock = {
  29982. secondsToVideoTs: secondsToVideoTs,
  29983. secondsToAudioTs: secondsToAudioTs,
  29984. videoTsToSeconds: videoTsToSeconds,
  29985. audioTsToSeconds: audioTsToSeconds,
  29986. audioTsToVideoTs: audioTsToVideoTs,
  29987. videoTsToAudioTs: videoTsToAudioTs
  29988. };
  29989. /**
  29990. * mux.js
  29991. *
  29992. * Copyright (c) Brightcove
  29993. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  29994. */
  29995. var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
  29996. /**
  29997. * Sum the `byteLength` properties of the data in each AAC frame
  29998. */
  29999. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  30000. var i,
  30001. currentObj,
  30002. sum = 0; // sum the byteLength's all each nal unit in the frame
  30003. for (i = 0; i < array.length; i++) {
  30004. currentObj = array[i];
  30005. sum += currentObj.data.byteLength;
  30006. }
  30007. return sum;
  30008. }; // Possibly pad (prefix) the audio track with silence if appending this track
  30009. // would lead to the introduction of a gap in the audio buffer
  30010. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  30011. var baseMediaDecodeTimeTs,
  30012. frameDuration = 0,
  30013. audioGapDuration = 0,
  30014. audioFillFrameCount = 0,
  30015. audioFillDuration = 0,
  30016. silentFrame,
  30017. i;
  30018. if (!frames.length) {
  30019. return;
  30020. }
  30021. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  30022. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
  30023. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  30024. // insert the shortest possible amount (audio gap or audio to video gap)
  30025. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  30026. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  30027. audioFillDuration = audioFillFrameCount * frameDuration;
  30028. } // don't attempt to fill gaps smaller than a single frame or larger
  30029. // than a half second
  30030. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
  30031. return;
  30032. }
  30033. silentFrame = silence[track.samplerate];
  30034. if (!silentFrame) {
  30035. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  30036. // from the content instead
  30037. silentFrame = frames[0].data;
  30038. }
  30039. for (i = 0; i < audioFillFrameCount; i++) {
  30040. frames.splice(i, 0, {
  30041. data: silentFrame
  30042. });
  30043. }
  30044. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  30045. }; // If the audio segment extends before the earliest allowed dts
  30046. // value, remove AAC frames until starts at or after the earliest
  30047. // allowed DTS so that we don't end up with a negative baseMedia-
  30048. // DecodeTime for the audio track
  30049. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  30050. if (track.minSegmentDts >= earliestAllowedDts) {
  30051. return adtsFrames;
  30052. } // We will need to recalculate the earliest segment Dts
  30053. track.minSegmentDts = Infinity;
  30054. return adtsFrames.filter(function (currentFrame) {
  30055. // If this is an allowed frame, keep it and record it's Dts
  30056. if (currentFrame.dts >= earliestAllowedDts) {
  30057. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  30058. track.minSegmentPts = track.minSegmentDts;
  30059. return true;
  30060. } // Otherwise, discard it
  30061. return false;
  30062. });
  30063. }; // generate the track's raw mdat data from an array of frames
  30064. var generateSampleTable$1 = function generateSampleTable(frames) {
  30065. var i,
  30066. currentFrame,
  30067. samples = [];
  30068. for (i = 0; i < frames.length; i++) {
  30069. currentFrame = frames[i];
  30070. samples.push({
  30071. size: currentFrame.data.byteLength,
  30072. duration: 1024 // For AAC audio, all samples contain 1024 samples
  30073. });
  30074. }
  30075. return samples;
  30076. }; // generate the track's sample table from an array of frames
  30077. var concatenateFrameData = function concatenateFrameData(frames) {
  30078. var i,
  30079. currentFrame,
  30080. dataOffset = 0,
  30081. data = new Uint8Array(sumFrameByteLengths(frames));
  30082. for (i = 0; i < frames.length; i++) {
  30083. currentFrame = frames[i];
  30084. data.set(currentFrame.data, dataOffset);
  30085. dataOffset += currentFrame.data.byteLength;
  30086. }
  30087. return data;
  30088. };
  30089. var audioFrameUtils = {
  30090. prefixWithSilence: prefixWithSilence,
  30091. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  30092. generateSampleTable: generateSampleTable$1,
  30093. concatenateFrameData: concatenateFrameData
  30094. };
  30095. /**
  30096. * mux.js
  30097. *
  30098. * Copyright (c) Brightcove
  30099. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  30100. */
  30101. var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
  30102. /**
  30103. * Store information about the start and end of the track and the
  30104. * duration for each frame/sample we process in order to calculate
  30105. * the baseMediaDecodeTime
  30106. */
  30107. var collectDtsInfo = function collectDtsInfo(track, data) {
  30108. if (typeof data.pts === 'number') {
  30109. if (track.timelineStartInfo.pts === undefined) {
  30110. track.timelineStartInfo.pts = data.pts;
  30111. }
  30112. if (track.minSegmentPts === undefined) {
  30113. track.minSegmentPts = data.pts;
  30114. } else {
  30115. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  30116. }
  30117. if (track.maxSegmentPts === undefined) {
  30118. track.maxSegmentPts = data.pts;
  30119. } else {
  30120. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  30121. }
  30122. }
  30123. if (typeof data.dts === 'number') {
  30124. if (track.timelineStartInfo.dts === undefined) {
  30125. track.timelineStartInfo.dts = data.dts;
  30126. }
  30127. if (track.minSegmentDts === undefined) {
  30128. track.minSegmentDts = data.dts;
  30129. } else {
  30130. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  30131. }
  30132. if (track.maxSegmentDts === undefined) {
  30133. track.maxSegmentDts = data.dts;
  30134. } else {
  30135. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  30136. }
  30137. }
  30138. };
  30139. /**
  30140. * Clear values used to calculate the baseMediaDecodeTime between
  30141. * tracks
  30142. */
  30143. var clearDtsInfo = function clearDtsInfo(track) {
  30144. delete track.minSegmentDts;
  30145. delete track.maxSegmentDts;
  30146. delete track.minSegmentPts;
  30147. delete track.maxSegmentPts;
  30148. };
  30149. /**
  30150. * Calculate the track's baseMediaDecodeTime based on the earliest
  30151. * DTS the transmuxer has ever seen and the minimum DTS for the
  30152. * current track
  30153. * @param track {object} track metadata configuration
  30154. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  30155. * in the source; false to adjust the first segment to start at 0.
  30156. */
  30157. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  30158. var baseMediaDecodeTime,
  30159. scale,
  30160. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  30161. if (!keepOriginalTimestamps) {
  30162. minSegmentDts -= track.timelineStartInfo.dts;
  30163. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  30164. // we want the start of the first segment to be placed
  30165. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  30166. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  30167. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  30168. if (track.type === 'audio') {
  30169. // Audio has a different clock equal to the sampling_rate so we need to
  30170. // scale the PTS values into the clock rate of the track
  30171. scale = track.samplerate / ONE_SECOND_IN_TS$2;
  30172. baseMediaDecodeTime *= scale;
  30173. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  30174. }
  30175. return baseMediaDecodeTime;
  30176. };
  30177. var trackDecodeInfo = {
  30178. clearDtsInfo: clearDtsInfo,
  30179. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  30180. collectDtsInfo: collectDtsInfo
  30181. };
  30182. /**
  30183. * mux.js
  30184. *
  30185. * Copyright (c) Brightcove
  30186. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  30187. *
  30188. * Reads in-band caption information from a video elementary
  30189. * stream. Captions must follow the CEA-708 standard for injection
  30190. * into an MPEG-2 transport streams.
  30191. * @see https://en.wikipedia.org/wiki/CEA-708
  30192. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  30193. */
  30194. // payload type field to indicate how they are to be
  30195. // interpreted. CEAS-708 caption content is always transmitted with
  30196. // payload type 0x04.
  30197. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  30198. RBSP_TRAILING_BITS = 128;
  30199. /**
  30200. * Parse a supplemental enhancement information (SEI) NAL unit.
  30201. * Stops parsing once a message of type ITU T T35 has been found.
  30202. *
  30203. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  30204. * @return {object} the parsed SEI payload
  30205. * @see Rec. ITU-T H.264, 7.3.2.3.1
  30206. */
  30207. var parseSei = function parseSei(bytes) {
  30208. var i = 0,
  30209. result = {
  30210. payloadType: -1,
  30211. payloadSize: 0
  30212. },
  30213. payloadType = 0,
  30214. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  30215. while (i < bytes.byteLength) {
  30216. // stop once we have hit the end of the sei_rbsp
  30217. if (bytes[i] === RBSP_TRAILING_BITS) {
  30218. break;
  30219. } // Parse payload type
  30220. while (bytes[i] === 0xFF) {
  30221. payloadType += 255;
  30222. i++;
  30223. }
  30224. payloadType += bytes[i++]; // Parse payload size
  30225. while (bytes[i] === 0xFF) {
  30226. payloadSize += 255;
  30227. i++;
  30228. }
  30229. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  30230. // there can only ever be one caption message in a frame's sei
  30231. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  30232. result.payloadType = payloadType;
  30233. result.payloadSize = payloadSize;
  30234. result.payload = bytes.subarray(i, i + payloadSize);
  30235. break;
  30236. } // skip the payload and parse the next message
  30237. i += payloadSize;
  30238. payloadType = 0;
  30239. payloadSize = 0;
  30240. }
  30241. return result;
  30242. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  30243. var parseUserData = function parseUserData(sei) {
  30244. // itu_t_t35_contry_code must be 181 (United States) for
  30245. // captions
  30246. if (sei.payload[0] !== 181) {
  30247. return null;
  30248. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  30249. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  30250. return null;
  30251. } // the user_identifier should be "GA94" to indicate ATSC1 data
  30252. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  30253. return null;
  30254. } // finally, user_data_type_code should be 0x03 for caption data
  30255. if (sei.payload[7] !== 0x03) {
  30256. return null;
  30257. } // return the user_data_type_structure and strip the trailing
  30258. // marker bits
  30259. return sei.payload.subarray(8, sei.payload.length - 1);
  30260. }; // see CEA-708-D, section 4.4
  30261. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  30262. var results = [],
  30263. i,
  30264. count,
  30265. offset,
  30266. data; // if this is just filler, return immediately
  30267. if (!(userData[0] & 0x40)) {
  30268. return results;
  30269. } // parse out the cc_data_1 and cc_data_2 fields
  30270. count = userData[0] & 0x1f;
  30271. for (i = 0; i < count; i++) {
  30272. offset = i * 3;
  30273. data = {
  30274. type: userData[offset + 2] & 0x03,
  30275. pts: pts
  30276. }; // capture cc data when cc_valid is 1
  30277. if (userData[offset + 2] & 0x04) {
  30278. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  30279. results.push(data);
  30280. }
  30281. }
  30282. return results;
  30283. };
  30284. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  30285. var length = data.byteLength,
  30286. emulationPreventionBytesPositions = [],
  30287. i = 1,
  30288. newLength,
  30289. newData; // Find all `Emulation Prevention Bytes`
  30290. while (i < length - 2) {
  30291. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  30292. emulationPreventionBytesPositions.push(i + 2);
  30293. i += 2;
  30294. } else {
  30295. i++;
  30296. }
  30297. } // If no Emulation Prevention Bytes were found just return the original
  30298. // array
  30299. if (emulationPreventionBytesPositions.length === 0) {
  30300. return data;
  30301. } // Create a new array to hold the NAL unit data
  30302. newLength = length - emulationPreventionBytesPositions.length;
  30303. newData = new Uint8Array(newLength);
  30304. var sourceIndex = 0;
  30305. for (i = 0; i < newLength; sourceIndex++, i++) {
  30306. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  30307. // Skip this byte
  30308. sourceIndex++; // Remove this position index
  30309. emulationPreventionBytesPositions.shift();
  30310. }
  30311. newData[i] = data[sourceIndex];
  30312. }
  30313. return newData;
  30314. }; // exports
  30315. var captionPacketParser = {
  30316. parseSei: parseSei,
  30317. parseUserData: parseUserData,
  30318. parseCaptionPackets: parseCaptionPackets,
  30319. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  30320. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  30321. };
  30322. // Link To Transport
  30323. // -----------------
  30324. var CaptionStream = function CaptionStream() {
  30325. CaptionStream.prototype.init.call(this);
  30326. this.captionPackets_ = [];
  30327. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  30328. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  30329. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  30330. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  30331. ];
  30332. this.reset(); // forward data and done events from CCs to this CaptionStream
  30333. this.ccStreams_.forEach(function (cc) {
  30334. cc.on('data', this.trigger.bind(this, 'data'));
  30335. cc.on('done', this.trigger.bind(this, 'done'));
  30336. }, this);
  30337. };
  30338. CaptionStream.prototype = new stream();
  30339. CaptionStream.prototype.push = function (event) {
  30340. var sei, userData, newCaptionPackets; // only examine SEI NALs
  30341. if (event.nalUnitType !== 'sei_rbsp') {
  30342. return;
  30343. } // parse the sei
  30344. sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
  30345. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  30346. return;
  30347. } // parse out the user data payload
  30348. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  30349. if (!userData) {
  30350. return;
  30351. } // Sometimes, the same segment # will be downloaded twice. To stop the
  30352. // caption data from being processed twice, we track the latest dts we've
  30353. // received and ignore everything with a dts before that. However, since
  30354. // data for a specific dts can be split across packets on either side of
  30355. // a segment boundary, we need to make sure we *don't* ignore the packets
  30356. // from the *next* segment that have dts === this.latestDts_. By constantly
  30357. // tracking the number of packets received with dts === this.latestDts_, we
  30358. // know how many should be ignored once we start receiving duplicates.
  30359. if (event.dts < this.latestDts_) {
  30360. // We've started getting older data, so set the flag.
  30361. this.ignoreNextEqualDts_ = true;
  30362. return;
  30363. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  30364. this.numSameDts_--;
  30365. if (!this.numSameDts_) {
  30366. // We've received the last duplicate packet, time to start processing again
  30367. this.ignoreNextEqualDts_ = false;
  30368. }
  30369. return;
  30370. } // parse out CC data packets and save them for later
  30371. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  30372. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  30373. if (this.latestDts_ !== event.dts) {
  30374. this.numSameDts_ = 0;
  30375. }
  30376. this.numSameDts_++;
  30377. this.latestDts_ = event.dts;
  30378. };
  30379. CaptionStream.prototype.flush = function () {
  30380. // make sure we actually parsed captions before proceeding
  30381. if (!this.captionPackets_.length) {
  30382. this.ccStreams_.forEach(function (cc) {
  30383. cc.flush();
  30384. }, this);
  30385. return;
  30386. } // In Chrome, the Array#sort function is not stable so add a
  30387. // presortIndex that we can use to ensure we get a stable-sort
  30388. this.captionPackets_.forEach(function (elem, idx) {
  30389. elem.presortIndex = idx;
  30390. }); // sort caption byte-pairs based on their PTS values
  30391. this.captionPackets_.sort(function (a, b) {
  30392. if (a.pts === b.pts) {
  30393. return a.presortIndex - b.presortIndex;
  30394. }
  30395. return a.pts - b.pts;
  30396. });
  30397. this.captionPackets_.forEach(function (packet) {
  30398. if (packet.type < 2) {
  30399. // Dispatch packet to the right Cea608Stream
  30400. this.dispatchCea608Packet(packet);
  30401. } // this is where an 'else' would go for a dispatching packets
  30402. // to a theoretical Cea708Stream that handles SERVICEn data
  30403. }, this);
  30404. this.captionPackets_.length = 0;
  30405. this.ccStreams_.forEach(function (cc) {
  30406. cc.flush();
  30407. }, this);
  30408. return;
  30409. };
  30410. CaptionStream.prototype.reset = function () {
  30411. this.latestDts_ = null;
  30412. this.ignoreNextEqualDts_ = false;
  30413. this.numSameDts_ = 0;
  30414. this.activeCea608Channel_ = [null, null];
  30415. this.ccStreams_.forEach(function (ccStream) {
  30416. ccStream.reset();
  30417. });
  30418. }; // From the CEA-608 spec:
  30419. /*
  30420. * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
  30421. * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
  30422. * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
  30423. * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
  30424. * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
  30425. * to switch to captioning or Text.
  30426. */
  30427. // With that in mind, we ignore any data between an XDS control code and a
  30428. // subsequent closed-captioning control code.
  30429. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  30430. // NOTE: packet.type is the CEA608 field
  30431. if (this.setsTextOrXDSActive(packet)) {
  30432. this.activeCea608Channel_[packet.type] = null;
  30433. } else if (this.setsChannel1Active(packet)) {
  30434. this.activeCea608Channel_[packet.type] = 0;
  30435. } else if (this.setsChannel2Active(packet)) {
  30436. this.activeCea608Channel_[packet.type] = 1;
  30437. }
  30438. if (this.activeCea608Channel_[packet.type] === null) {
  30439. // If we haven't received anything to set the active channel, or the
  30440. // packets are Text/XDS data, discard the data; we don't want jumbled
  30441. // captions
  30442. return;
  30443. }
  30444. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  30445. };
  30446. CaptionStream.prototype.setsChannel1Active = function (packet) {
  30447. return (packet.ccData & 0x7800) === 0x1000;
  30448. };
  30449. CaptionStream.prototype.setsChannel2Active = function (packet) {
  30450. return (packet.ccData & 0x7800) === 0x1800;
  30451. };
  30452. CaptionStream.prototype.setsTextOrXDSActive = function (packet) {
  30453. return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
  30454. }; // ----------------------
  30455. // Session to Application
  30456. // ----------------------
  30457. // This hash maps non-ASCII, special, and extended character codes to their
  30458. // proper Unicode equivalent. The first keys that are only a single byte
  30459. // are the non-standard ASCII characters, which simply map the CEA608 byte
  30460. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  30461. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  30462. // can be performed regardless of the field and data channel on which the
  30463. // character code was received.
  30464. var CHARACTER_TRANSLATION = {
  30465. 0x2a: 0xe1,
  30466. // á
  30467. 0x5c: 0xe9,
  30468. // é
  30469. 0x5e: 0xed,
  30470. // í
  30471. 0x5f: 0xf3,
  30472. // ó
  30473. 0x60: 0xfa,
  30474. // ú
  30475. 0x7b: 0xe7,
  30476. // ç
  30477. 0x7c: 0xf7,
  30478. // ÷
  30479. 0x7d: 0xd1,
  30480. // Ñ
  30481. 0x7e: 0xf1,
  30482. // ñ
  30483. 0x7f: 0x2588,
  30484. // █
  30485. 0x0130: 0xae,
  30486. // ®
  30487. 0x0131: 0xb0,
  30488. // °
  30489. 0x0132: 0xbd,
  30490. // ½
  30491. 0x0133: 0xbf,
  30492. // ¿
  30493. 0x0134: 0x2122,
  30494. // ™
  30495. 0x0135: 0xa2,
  30496. // ¢
  30497. 0x0136: 0xa3,
  30498. // £
  30499. 0x0137: 0x266a,
  30500. // ♪
  30501. 0x0138: 0xe0,
  30502. // à
  30503. 0x0139: 0xa0,
  30504. //
  30505. 0x013a: 0xe8,
  30506. // è
  30507. 0x013b: 0xe2,
  30508. // â
  30509. 0x013c: 0xea,
  30510. // ê
  30511. 0x013d: 0xee,
  30512. // î
  30513. 0x013e: 0xf4,
  30514. // ô
  30515. 0x013f: 0xfb,
  30516. // û
  30517. 0x0220: 0xc1,
  30518. // Á
  30519. 0x0221: 0xc9,
  30520. // É
  30521. 0x0222: 0xd3,
  30522. // Ó
  30523. 0x0223: 0xda,
  30524. // Ú
  30525. 0x0224: 0xdc,
  30526. // Ü
  30527. 0x0225: 0xfc,
  30528. // ü
  30529. 0x0226: 0x2018,
  30530. // ‘
  30531. 0x0227: 0xa1,
  30532. // ¡
  30533. 0x0228: 0x2a,
  30534. // *
  30535. 0x0229: 0x27,
  30536. // '
  30537. 0x022a: 0x2014,
  30538. // —
  30539. 0x022b: 0xa9,
  30540. // ©
  30541. 0x022c: 0x2120,
  30542. // ℠
  30543. 0x022d: 0x2022,
  30544. // •
  30545. 0x022e: 0x201c,
  30546. // “
  30547. 0x022f: 0x201d,
  30548. // ”
  30549. 0x0230: 0xc0,
  30550. // À
  30551. 0x0231: 0xc2,
  30552. // Â
  30553. 0x0232: 0xc7,
  30554. // Ç
  30555. 0x0233: 0xc8,
  30556. // È
  30557. 0x0234: 0xca,
  30558. // Ê
  30559. 0x0235: 0xcb,
  30560. // Ë
  30561. 0x0236: 0xeb,
  30562. // ë
  30563. 0x0237: 0xce,
  30564. // Î
  30565. 0x0238: 0xcf,
  30566. // Ï
  30567. 0x0239: 0xef,
  30568. // ï
  30569. 0x023a: 0xd4,
  30570. // Ô
  30571. 0x023b: 0xd9,
  30572. // Ù
  30573. 0x023c: 0xf9,
  30574. // ù
  30575. 0x023d: 0xdb,
  30576. // Û
  30577. 0x023e: 0xab,
  30578. // «
  30579. 0x023f: 0xbb,
  30580. // »
  30581. 0x0320: 0xc3,
  30582. // Ã
  30583. 0x0321: 0xe3,
  30584. // ã
  30585. 0x0322: 0xcd,
  30586. // Í
  30587. 0x0323: 0xcc,
  30588. // Ì
  30589. 0x0324: 0xec,
  30590. // ì
  30591. 0x0325: 0xd2,
  30592. // Ò
  30593. 0x0326: 0xf2,
  30594. // ò
  30595. 0x0327: 0xd5,
  30596. // Õ
  30597. 0x0328: 0xf5,
  30598. // õ
  30599. 0x0329: 0x7b,
  30600. // {
  30601. 0x032a: 0x7d,
  30602. // }
  30603. 0x032b: 0x5c,
  30604. // \
  30605. 0x032c: 0x5e,
  30606. // ^
  30607. 0x032d: 0x5f,
  30608. // _
  30609. 0x032e: 0x7c,
  30610. // |
  30611. 0x032f: 0x7e,
  30612. // ~
  30613. 0x0330: 0xc4,
  30614. // Ä
  30615. 0x0331: 0xe4,
  30616. // ä
  30617. 0x0332: 0xd6,
  30618. // Ö
  30619. 0x0333: 0xf6,
  30620. // ö
  30621. 0x0334: 0xdf,
  30622. // ß
  30623. 0x0335: 0xa5,
  30624. // ¥
  30625. 0x0336: 0xa4,
  30626. // ¤
  30627. 0x0337: 0x2502,
  30628. // │
  30629. 0x0338: 0xc5,
  30630. // Å
  30631. 0x0339: 0xe5,
  30632. // å
  30633. 0x033a: 0xd8,
  30634. // Ø
  30635. 0x033b: 0xf8,
  30636. // ø
  30637. 0x033c: 0x250c,
  30638. // ┌
  30639. 0x033d: 0x2510,
  30640. // ┐
  30641. 0x033e: 0x2514,
  30642. // └
  30643. 0x033f: 0x2518 // ┘
  30644. };
  30645. var getCharFromCode = function getCharFromCode(code) {
  30646. if (code === null) {
  30647. return '';
  30648. }
  30649. code = CHARACTER_TRANSLATION[code] || code;
  30650. return String.fromCharCode(code);
  30651. }; // the index of the last row in a CEA-608 display buffer
  30652. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  30653. // getting it through bit logic.
  30654. 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
  30655. // cells. The "bottom" row is the last element in the outer array.
  30656. var createDisplayBuffer = function createDisplayBuffer() {
  30657. var result = [],
  30658. i = BOTTOM_ROW + 1;
  30659. while (i--) {
  30660. result.push('');
  30661. }
  30662. return result;
  30663. };
  30664. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  30665. Cea608Stream.prototype.init.call(this);
  30666. this.field_ = field || 0;
  30667. this.dataChannel_ = dataChannel || 0;
  30668. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  30669. this.setConstants();
  30670. this.reset();
  30671. this.push = function (packet) {
  30672. var data, swap, char0, char1, text; // remove the parity bits
  30673. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  30674. if (data === this.lastControlCode_) {
  30675. this.lastControlCode_ = null;
  30676. return;
  30677. } // Store control codes
  30678. if ((data & 0xf000) === 0x1000) {
  30679. this.lastControlCode_ = data;
  30680. } else if (data !== this.PADDING_) {
  30681. this.lastControlCode_ = null;
  30682. }
  30683. char0 = data >>> 8;
  30684. char1 = data & 0xff;
  30685. if (data === this.PADDING_) {
  30686. return;
  30687. } else if (data === this.RESUME_CAPTION_LOADING_) {
  30688. this.mode_ = 'popOn';
  30689. } else if (data === this.END_OF_CAPTION_) {
  30690. // If an EOC is received while in paint-on mode, the displayed caption
  30691. // text should be swapped to non-displayed memory as if it was a pop-on
  30692. // caption. Because of that, we should explicitly switch back to pop-on
  30693. // mode
  30694. this.mode_ = 'popOn';
  30695. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  30696. this.flushDisplayed(packet.pts); // flip memory
  30697. swap = this.displayed_;
  30698. this.displayed_ = this.nonDisplayed_;
  30699. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  30700. this.startPts_ = packet.pts;
  30701. } else if (data === this.ROLL_UP_2_ROWS_) {
  30702. this.rollUpRows_ = 2;
  30703. this.setRollUp(packet.pts);
  30704. } else if (data === this.ROLL_UP_3_ROWS_) {
  30705. this.rollUpRows_ = 3;
  30706. this.setRollUp(packet.pts);
  30707. } else if (data === this.ROLL_UP_4_ROWS_) {
  30708. this.rollUpRows_ = 4;
  30709. this.setRollUp(packet.pts);
  30710. } else if (data === this.CARRIAGE_RETURN_) {
  30711. this.clearFormatting(packet.pts);
  30712. this.flushDisplayed(packet.pts);
  30713. this.shiftRowsUp_();
  30714. this.startPts_ = packet.pts;
  30715. } else if (data === this.BACKSPACE_) {
  30716. if (this.mode_ === 'popOn') {
  30717. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  30718. } else {
  30719. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  30720. }
  30721. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  30722. this.flushDisplayed(packet.pts);
  30723. this.displayed_ = createDisplayBuffer();
  30724. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  30725. this.nonDisplayed_ = createDisplayBuffer();
  30726. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  30727. if (this.mode_ !== 'paintOn') {
  30728. // NOTE: This should be removed when proper caption positioning is
  30729. // implemented
  30730. this.flushDisplayed(packet.pts);
  30731. this.displayed_ = createDisplayBuffer();
  30732. }
  30733. this.mode_ = 'paintOn';
  30734. this.startPts_ = packet.pts; // Append special characters to caption text
  30735. } else if (this.isSpecialCharacter(char0, char1)) {
  30736. // Bitmask char0 so that we can apply character transformations
  30737. // regardless of field and data channel.
  30738. // Then byte-shift to the left and OR with char1 so we can pass the
  30739. // entire character code to `getCharFromCode`.
  30740. char0 = (char0 & 0x03) << 8;
  30741. text = getCharFromCode(char0 | char1);
  30742. this[this.mode_](packet.pts, text);
  30743. this.column_++; // Append extended characters to caption text
  30744. } else if (this.isExtCharacter(char0, char1)) {
  30745. // Extended characters always follow their "non-extended" equivalents.
  30746. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  30747. // decoders are supposed to drop the "è", while compliant decoders
  30748. // backspace the "e" and insert "è".
  30749. // Delete the previous character
  30750. if (this.mode_ === 'popOn') {
  30751. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  30752. } else {
  30753. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  30754. } // Bitmask char0 so that we can apply character transformations
  30755. // regardless of field and data channel.
  30756. // Then byte-shift to the left and OR with char1 so we can pass the
  30757. // entire character code to `getCharFromCode`.
  30758. char0 = (char0 & 0x03) << 8;
  30759. text = getCharFromCode(char0 | char1);
  30760. this[this.mode_](packet.pts, text);
  30761. this.column_++; // Process mid-row codes
  30762. } else if (this.isMidRowCode(char0, char1)) {
  30763. // Attributes are not additive, so clear all formatting
  30764. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  30765. // should be replaced with spaces, so add one now
  30766. this[this.mode_](packet.pts, ' ');
  30767. this.column_++;
  30768. if ((char1 & 0xe) === 0xe) {
  30769. this.addFormatting(packet.pts, ['i']);
  30770. }
  30771. if ((char1 & 0x1) === 0x1) {
  30772. this.addFormatting(packet.pts, ['u']);
  30773. } // Detect offset control codes and adjust cursor
  30774. } else if (this.isOffsetControlCode(char0, char1)) {
  30775. // Cursor position is set by indent PAC (see below) in 4-column
  30776. // increments, with an additional offset code of 1-3 to reach any
  30777. // of the 32 columns specified by CEA-608. So all we need to do
  30778. // here is increment the column cursor by the given offset.
  30779. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  30780. } else if (this.isPAC(char0, char1)) {
  30781. // There's no logic for PAC -> row mapping, so we have to just
  30782. // find the row code in an array and use its index :(
  30783. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  30784. if (this.mode_ === 'rollUp') {
  30785. // This implies that the base row is incorrectly set.
  30786. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  30787. // of roll-up rows set.
  30788. if (row - this.rollUpRows_ + 1 < 0) {
  30789. row = this.rollUpRows_ - 1;
  30790. }
  30791. this.setRollUp(packet.pts, row);
  30792. }
  30793. if (row !== this.row_) {
  30794. // formatting is only persistent for current row
  30795. this.clearFormatting(packet.pts);
  30796. this.row_ = row;
  30797. } // All PACs can apply underline, so detect and apply
  30798. // (All odd-numbered second bytes set underline)
  30799. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  30800. this.addFormatting(packet.pts, ['u']);
  30801. }
  30802. if ((data & 0x10) === 0x10) {
  30803. // We've got an indent level code. Each successive even number
  30804. // increments the column cursor by 4, so we can get the desired
  30805. // column position by bit-shifting to the right (to get n/2)
  30806. // and multiplying by 4.
  30807. this.column_ = ((data & 0xe) >> 1) * 4;
  30808. }
  30809. if (this.isColorPAC(char1)) {
  30810. // it's a color code, though we only support white, which
  30811. // can be either normal or italicized. white italics can be
  30812. // either 0x4e or 0x6e depending on the row, so we just
  30813. // bitwise-and with 0xe to see if italics should be turned on
  30814. if ((char1 & 0xe) === 0xe) {
  30815. this.addFormatting(packet.pts, ['i']);
  30816. }
  30817. } // We have a normal character in char0, and possibly one in char1
  30818. } else if (this.isNormalChar(char0)) {
  30819. if (char1 === 0x00) {
  30820. char1 = null;
  30821. }
  30822. text = getCharFromCode(char0);
  30823. text += getCharFromCode(char1);
  30824. this[this.mode_](packet.pts, text);
  30825. this.column_ += text.length;
  30826. } // finish data processing
  30827. };
  30828. };
  30829. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  30830. // display buffer
  30831. Cea608Stream.prototype.flushDisplayed = function (pts) {
  30832. var content = this.displayed_ // remove spaces from the start and end of the string
  30833. .map(function (row) {
  30834. try {
  30835. return row.trim();
  30836. } catch (e) {
  30837. // Ordinarily, this shouldn't happen. However, caption
  30838. // parsing errors should not throw exceptions and
  30839. // break playback.
  30840. // eslint-disable-next-line no-console
  30841. console.error('Skipping malformed caption.');
  30842. return '';
  30843. }
  30844. }) // combine all text rows to display in one cue
  30845. .join('\n') // and remove blank rows from the start and end, but not the middle
  30846. .replace(/^\n+|\n+$/g, '');
  30847. if (content.length) {
  30848. this.trigger('data', {
  30849. startPts: this.startPts_,
  30850. endPts: pts,
  30851. text: content,
  30852. stream: this.name_
  30853. });
  30854. }
  30855. };
  30856. /**
  30857. * Zero out the data, used for startup and on seek
  30858. */
  30859. Cea608Stream.prototype.reset = function () {
  30860. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  30861. // actually display captions. If a caption is shifted to a row
  30862. // with a lower index than this, it is cleared from the display
  30863. // buffer
  30864. this.topRow_ = 0;
  30865. this.startPts_ = 0;
  30866. this.displayed_ = createDisplayBuffer();
  30867. this.nonDisplayed_ = createDisplayBuffer();
  30868. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  30869. this.column_ = 0;
  30870. this.row_ = BOTTOM_ROW;
  30871. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  30872. this.formatting_ = [];
  30873. };
  30874. /**
  30875. * Sets up control code and related constants for this instance
  30876. */
  30877. Cea608Stream.prototype.setConstants = function () {
  30878. // The following attributes have these uses:
  30879. // ext_ : char0 for mid-row codes, and the base for extended
  30880. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  30881. // extended codes)
  30882. // control_: char0 for control codes, except byte-shifted to the
  30883. // left so that we can do this.control_ | CONTROL_CODE
  30884. // offset_: char0 for tab offset codes
  30885. //
  30886. // It's also worth noting that control codes, and _only_ control codes,
  30887. // differ between field 1 and field2. Field 2 control codes are always
  30888. // their field 1 value plus 1. That's why there's the "| field" on the
  30889. // control value.
  30890. if (this.dataChannel_ === 0) {
  30891. this.BASE_ = 0x10;
  30892. this.EXT_ = 0x11;
  30893. this.CONTROL_ = (0x14 | this.field_) << 8;
  30894. this.OFFSET_ = 0x17;
  30895. } else if (this.dataChannel_ === 1) {
  30896. this.BASE_ = 0x18;
  30897. this.EXT_ = 0x19;
  30898. this.CONTROL_ = (0x1c | this.field_) << 8;
  30899. this.OFFSET_ = 0x1f;
  30900. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  30901. // list is not exhaustive. For a more comprehensive listing and semantics see
  30902. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  30903. // Padding
  30904. this.PADDING_ = 0x0000; // Pop-on Mode
  30905. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  30906. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  30907. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  30908. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  30909. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  30910. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  30911. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  30912. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  30913. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  30914. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  30915. };
  30916. /**
  30917. * Detects if the 2-byte packet data is a special character
  30918. *
  30919. * Special characters have a second byte in the range 0x30 to 0x3f,
  30920. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  30921. * data channel 2).
  30922. *
  30923. * @param {Integer} char0 The first byte
  30924. * @param {Integer} char1 The second byte
  30925. * @return {Boolean} Whether the 2 bytes are an special character
  30926. */
  30927. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  30928. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  30929. };
  30930. /**
  30931. * Detects if the 2-byte packet data is an extended character
  30932. *
  30933. * Extended characters have a second byte in the range 0x20 to 0x3f,
  30934. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  30935. * 0x1a or 0x1b (for data channel 2).
  30936. *
  30937. * @param {Integer} char0 The first byte
  30938. * @param {Integer} char1 The second byte
  30939. * @return {Boolean} Whether the 2 bytes are an extended character
  30940. */
  30941. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  30942. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  30943. };
  30944. /**
  30945. * Detects if the 2-byte packet is a mid-row code
  30946. *
  30947. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  30948. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  30949. * channel 2).
  30950. *
  30951. * @param {Integer} char0 The first byte
  30952. * @param {Integer} char1 The second byte
  30953. * @return {Boolean} Whether the 2 bytes are a mid-row code
  30954. */
  30955. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  30956. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  30957. };
  30958. /**
  30959. * Detects if the 2-byte packet is an offset control code
  30960. *
  30961. * Offset control codes have a second byte in the range 0x21 to 0x23,
  30962. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  30963. * data channel 2).
  30964. *
  30965. * @param {Integer} char0 The first byte
  30966. * @param {Integer} char1 The second byte
  30967. * @return {Boolean} Whether the 2 bytes are an offset control code
  30968. */
  30969. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  30970. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  30971. };
  30972. /**
  30973. * Detects if the 2-byte packet is a Preamble Address Code
  30974. *
  30975. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  30976. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  30977. * range 0x40 to 0x7f.
  30978. *
  30979. * @param {Integer} char0 The first byte
  30980. * @param {Integer} char1 The second byte
  30981. * @return {Boolean} Whether the 2 bytes are a PAC
  30982. */
  30983. Cea608Stream.prototype.isPAC = function (char0, char1) {
  30984. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  30985. };
  30986. /**
  30987. * Detects if a packet's second byte is in the range of a PAC color code
  30988. *
  30989. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  30990. * 0x60 to 0x6f.
  30991. *
  30992. * @param {Integer} char1 The second byte
  30993. * @return {Boolean} Whether the byte is a color PAC
  30994. */
  30995. Cea608Stream.prototype.isColorPAC = function (char1) {
  30996. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  30997. };
  30998. /**
  30999. * Detects if a single byte is in the range of a normal character
  31000. *
  31001. * Normal text bytes are in the range 0x20 to 0x7f.
  31002. *
  31003. * @param {Integer} char The byte
  31004. * @return {Boolean} Whether the byte is a normal character
  31005. */
  31006. Cea608Stream.prototype.isNormalChar = function (_char) {
  31007. return _char >= 0x20 && _char <= 0x7f;
  31008. };
  31009. /**
  31010. * Configures roll-up
  31011. *
  31012. * @param {Integer} pts Current PTS
  31013. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  31014. * a new position
  31015. */
  31016. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  31017. // Reset the base row to the bottom row when switching modes
  31018. if (this.mode_ !== 'rollUp') {
  31019. this.row_ = BOTTOM_ROW;
  31020. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  31021. this.flushDisplayed(pts);
  31022. this.nonDisplayed_ = createDisplayBuffer();
  31023. this.displayed_ = createDisplayBuffer();
  31024. }
  31025. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  31026. // move currently displayed captions (up or down) to the new base row
  31027. for (var i = 0; i < this.rollUpRows_; i++) {
  31028. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  31029. this.displayed_[this.row_ - i] = '';
  31030. }
  31031. }
  31032. if (newBaseRow === undefined) {
  31033. newBaseRow = this.row_;
  31034. }
  31035. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  31036. }; // Adds the opening HTML tag for the passed character to the caption text,
  31037. // and keeps track of it for later closing
  31038. Cea608Stream.prototype.addFormatting = function (pts, format) {
  31039. this.formatting_ = this.formatting_.concat(format);
  31040. var text = format.reduce(function (text, format) {
  31041. return text + '<' + format + '>';
  31042. }, '');
  31043. this[this.mode_](pts, text);
  31044. }; // Adds HTML closing tags for current formatting to caption text and
  31045. // clears remembered formatting
  31046. Cea608Stream.prototype.clearFormatting = function (pts) {
  31047. if (!this.formatting_.length) {
  31048. return;
  31049. }
  31050. var text = this.formatting_.reverse().reduce(function (text, format) {
  31051. return text + '</' + format + '>';
  31052. }, '');
  31053. this.formatting_ = [];
  31054. this[this.mode_](pts, text);
  31055. }; // Mode Implementations
  31056. Cea608Stream.prototype.popOn = function (pts, text) {
  31057. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  31058. baseRow += text;
  31059. this.nonDisplayed_[this.row_] = baseRow;
  31060. };
  31061. Cea608Stream.prototype.rollUp = function (pts, text) {
  31062. var baseRow = this.displayed_[this.row_];
  31063. baseRow += text;
  31064. this.displayed_[this.row_] = baseRow;
  31065. };
  31066. Cea608Stream.prototype.shiftRowsUp_ = function () {
  31067. var i; // clear out inactive rows
  31068. for (i = 0; i < this.topRow_; i++) {
  31069. this.displayed_[i] = '';
  31070. }
  31071. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  31072. this.displayed_[i] = '';
  31073. } // shift displayed rows up
  31074. for (i = this.topRow_; i < this.row_; i++) {
  31075. this.displayed_[i] = this.displayed_[i + 1];
  31076. } // clear out the bottom row
  31077. this.displayed_[this.row_] = '';
  31078. };
  31079. Cea608Stream.prototype.paintOn = function (pts, text) {
  31080. var baseRow = this.displayed_[this.row_];
  31081. baseRow += text;
  31082. this.displayed_[this.row_] = baseRow;
  31083. }; // exports
  31084. var captionStream = {
  31085. CaptionStream: CaptionStream,
  31086. Cea608Stream: Cea608Stream
  31087. };
  31088. /**
  31089. * mux.js
  31090. *
  31091. * Copyright (c) Brightcove
  31092. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  31093. */
  31094. var streamTypes = {
  31095. H264_STREAM_TYPE: 0x1B,
  31096. ADTS_STREAM_TYPE: 0x0F,
  31097. METADATA_STREAM_TYPE: 0x15
  31098. };
  31099. var MAX_TS = 8589934592;
  31100. var RO_THRESH = 4294967296;
  31101. var handleRollover = function handleRollover(value, reference) {
  31102. var direction = 1;
  31103. if (value > reference) {
  31104. // If the current timestamp value is greater than our reference timestamp and we detect a
  31105. // timestamp rollover, this means the roll over is happening in the opposite direction.
  31106. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  31107. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  31108. // rollover point. In loading this segment, the timestamp values will be very large,
  31109. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  31110. // the time stamp to be `value - 2^33`.
  31111. direction = -1;
  31112. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  31113. // cause an incorrect adjustment.
  31114. while (Math.abs(reference - value) > RO_THRESH) {
  31115. value += direction * MAX_TS;
  31116. }
  31117. return value;
  31118. };
  31119. var TimestampRolloverStream = function TimestampRolloverStream(type) {
  31120. var lastDTS, referenceDTS;
  31121. TimestampRolloverStream.prototype.init.call(this);
  31122. this.type_ = type;
  31123. this.push = function (data) {
  31124. if (data.type !== this.type_) {
  31125. return;
  31126. }
  31127. if (referenceDTS === undefined) {
  31128. referenceDTS = data.dts;
  31129. }
  31130. data.dts = handleRollover(data.dts, referenceDTS);
  31131. data.pts = handleRollover(data.pts, referenceDTS);
  31132. lastDTS = data.dts;
  31133. this.trigger('data', data);
  31134. };
  31135. this.flush = function () {
  31136. referenceDTS = lastDTS;
  31137. this.trigger('done');
  31138. };
  31139. this.discontinuity = function () {
  31140. referenceDTS = void 0;
  31141. lastDTS = void 0;
  31142. };
  31143. };
  31144. TimestampRolloverStream.prototype = new stream();
  31145. var timestampRolloverStream = {
  31146. TimestampRolloverStream: TimestampRolloverStream,
  31147. handleRollover: handleRollover
  31148. };
  31149. var percentEncode = function percentEncode(bytes, start, end) {
  31150. var i,
  31151. result = '';
  31152. for (i = start; i < end; i++) {
  31153. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  31154. }
  31155. return result;
  31156. },
  31157. // return the string representation of the specified byte range,
  31158. // interpreted as UTf-8.
  31159. parseUtf8 = function parseUtf8(bytes, start, end) {
  31160. return decodeURIComponent(percentEncode(bytes, start, end));
  31161. },
  31162. // return the string representation of the specified byte range,
  31163. // interpreted as ISO-8859-1.
  31164. parseIso88591 = function parseIso88591(bytes, start, end) {
  31165. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  31166. },
  31167. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  31168. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  31169. },
  31170. tagParsers = {
  31171. TXXX: function TXXX(tag) {
  31172. var i;
  31173. if (tag.data[0] !== 3) {
  31174. // ignore frames with unrecognized character encodings
  31175. return;
  31176. }
  31177. for (i = 1; i < tag.data.length; i++) {
  31178. if (tag.data[i] === 0) {
  31179. // parse the text fields
  31180. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  31181. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  31182. break;
  31183. }
  31184. }
  31185. tag.data = tag.value;
  31186. },
  31187. WXXX: function WXXX(tag) {
  31188. var i;
  31189. if (tag.data[0] !== 3) {
  31190. // ignore frames with unrecognized character encodings
  31191. return;
  31192. }
  31193. for (i = 1; i < tag.data.length; i++) {
  31194. if (tag.data[i] === 0) {
  31195. // parse the description and URL fields
  31196. tag.description = parseUtf8(tag.data, 1, i);
  31197. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  31198. break;
  31199. }
  31200. }
  31201. },
  31202. PRIV: function PRIV(tag) {
  31203. var i;
  31204. for (i = 0; i < tag.data.length; i++) {
  31205. if (tag.data[i] === 0) {
  31206. // parse the description and URL fields
  31207. tag.owner = parseIso88591(tag.data, 0, i);
  31208. break;
  31209. }
  31210. }
  31211. tag.privateData = tag.data.subarray(i + 1);
  31212. tag.data = tag.privateData;
  31213. }
  31214. },
  31215. _MetadataStream;
  31216. _MetadataStream = function MetadataStream(options) {
  31217. var settings = {
  31218. debug: !!(options && options.debug),
  31219. // the bytes of the program-level descriptor field in MP2T
  31220. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  31221. // program element descriptors"
  31222. descriptor: options && options.descriptor
  31223. },
  31224. // the total size in bytes of the ID3 tag being parsed
  31225. tagSize = 0,
  31226. // tag data that is not complete enough to be parsed
  31227. buffer = [],
  31228. // the total number of bytes currently in the buffer
  31229. bufferSize = 0,
  31230. i;
  31231. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  31232. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  31233. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  31234. if (settings.descriptor) {
  31235. for (i = 0; i < settings.descriptor.length; i++) {
  31236. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  31237. }
  31238. }
  31239. this.push = function (chunk) {
  31240. var tag, frameStart, frameSize, frame, i, frameHeader;
  31241. if (chunk.type !== 'timed-metadata') {
  31242. return;
  31243. } // if data_alignment_indicator is set in the PES header,
  31244. // we must have the start of a new ID3 tag. Assume anything
  31245. // remaining in the buffer was malformed and throw it out
  31246. if (chunk.dataAlignmentIndicator) {
  31247. bufferSize = 0;
  31248. buffer.length = 0;
  31249. } // ignore events that don't look like ID3 data
  31250. 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))) {
  31251. if (settings.debug) {
  31252. // eslint-disable-next-line no-console
  31253. console.log('Skipping unrecognized metadata packet');
  31254. }
  31255. return;
  31256. } // add this chunk to the data we've collected so far
  31257. buffer.push(chunk);
  31258. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  31259. if (buffer.length === 1) {
  31260. // the frame size is transmitted as a 28-bit integer in the
  31261. // last four bytes of the ID3 header.
  31262. // The most significant bit of each byte is dropped and the
  31263. // results concatenated to recover the actual value.
  31264. tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  31265. // convenient for our comparisons to include it
  31266. tagSize += 10;
  31267. } // if the entire frame has not arrived, wait for more data
  31268. if (bufferSize < tagSize) {
  31269. return;
  31270. } // collect the entire frame so it can be parsed
  31271. tag = {
  31272. data: new Uint8Array(tagSize),
  31273. frames: [],
  31274. pts: buffer[0].pts,
  31275. dts: buffer[0].dts
  31276. };
  31277. for (i = 0; i < tagSize;) {
  31278. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  31279. i += buffer[0].data.byteLength;
  31280. bufferSize -= buffer[0].data.byteLength;
  31281. buffer.shift();
  31282. } // find the start of the first frame and the end of the tag
  31283. frameStart = 10;
  31284. if (tag.data[5] & 0x40) {
  31285. // advance the frame start past the extended header
  31286. frameStart += 4; // header size field
  31287. frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  31288. tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
  31289. } // parse one or more ID3 frames
  31290. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  31291. do {
  31292. // determine the number of bytes in this frame
  31293. frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  31294. if (frameSize < 1) {
  31295. // eslint-disable-next-line no-console
  31296. return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
  31297. }
  31298. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  31299. frame = {
  31300. id: frameHeader,
  31301. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  31302. };
  31303. frame.key = frame.id;
  31304. if (tagParsers[frame.id]) {
  31305. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  31306. // time for raw AAC data
  31307. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  31308. var d = frame.data,
  31309. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  31310. size *= 4;
  31311. size += d[7] & 0x03;
  31312. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  31313. // on the value of this frame
  31314. // we couldn't have known the appropriate pts and dts before
  31315. // parsing this ID3 tag so set those values now
  31316. if (tag.pts === undefined && tag.dts === undefined) {
  31317. tag.pts = frame.timeStamp;
  31318. tag.dts = frame.timeStamp;
  31319. }
  31320. this.trigger('timestamp', frame);
  31321. }
  31322. }
  31323. tag.frames.push(frame);
  31324. frameStart += 10; // advance past the frame header
  31325. frameStart += frameSize; // advance past the frame body
  31326. } while (frameStart < tagSize);
  31327. this.trigger('data', tag);
  31328. };
  31329. };
  31330. _MetadataStream.prototype = new stream();
  31331. var metadataStream = _MetadataStream;
  31332. var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
  31333. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  31334. var MP2T_PACKET_LENGTH = 188,
  31335. // bytes
  31336. SYNC_BYTE = 0x47;
  31337. /**
  31338. * Splits an incoming stream of binary data into MPEG-2 Transport
  31339. * Stream packets.
  31340. */
  31341. _TransportPacketStream = function TransportPacketStream() {
  31342. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  31343. bytesInBuffer = 0;
  31344. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  31345. /**
  31346. * Split a stream of data into M2TS packets
  31347. **/
  31348. this.push = function (bytes) {
  31349. var startIndex = 0,
  31350. endIndex = MP2T_PACKET_LENGTH,
  31351. everything; // If there are bytes remaining from the last segment, prepend them to the
  31352. // bytes that were pushed in
  31353. if (bytesInBuffer) {
  31354. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  31355. everything.set(buffer.subarray(0, bytesInBuffer));
  31356. everything.set(bytes, bytesInBuffer);
  31357. bytesInBuffer = 0;
  31358. } else {
  31359. everything = bytes;
  31360. } // While we have enough data for a packet
  31361. while (endIndex < everything.byteLength) {
  31362. // Look for a pair of start and end sync bytes in the data..
  31363. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  31364. // We found a packet so emit it and jump one whole packet forward in
  31365. // the stream
  31366. this.trigger('data', everything.subarray(startIndex, endIndex));
  31367. startIndex += MP2T_PACKET_LENGTH;
  31368. endIndex += MP2T_PACKET_LENGTH;
  31369. continue;
  31370. } // If we get here, we have somehow become de-synchronized and we need to step
  31371. // forward one byte at a time until we find a pair of sync bytes that denote
  31372. // a packet
  31373. startIndex++;
  31374. endIndex++;
  31375. } // If there was some data left over at the end of the segment that couldn't
  31376. // possibly be a whole packet, keep it because it might be the start of a packet
  31377. // that continues in the next segment
  31378. if (startIndex < everything.byteLength) {
  31379. buffer.set(everything.subarray(startIndex), 0);
  31380. bytesInBuffer = everything.byteLength - startIndex;
  31381. }
  31382. };
  31383. /**
  31384. * Passes identified M2TS packets to the TransportParseStream to be parsed
  31385. **/
  31386. this.flush = function () {
  31387. // If the buffer contains a whole packet when we are being flushed, emit it
  31388. // and empty the buffer. Otherwise hold onto the data because it may be
  31389. // important for decoding the next segment
  31390. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  31391. this.trigger('data', buffer);
  31392. bytesInBuffer = 0;
  31393. }
  31394. this.trigger('done');
  31395. };
  31396. };
  31397. _TransportPacketStream.prototype = new stream();
  31398. /**
  31399. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  31400. * forms of the individual transport stream packets.
  31401. */
  31402. _TransportParseStream = function TransportParseStream() {
  31403. var parsePsi, parsePat, parsePmt, self;
  31404. _TransportParseStream.prototype.init.call(this);
  31405. self = this;
  31406. this.packetsWaitingForPmt = [];
  31407. this.programMapTable = undefined;
  31408. parsePsi = function parsePsi(payload, psi) {
  31409. var offset = 0; // PSI packets may be split into multiple sections and those
  31410. // sections may be split into multiple packets. If a PSI
  31411. // section starts in this packet, the payload_unit_start_indicator
  31412. // will be true and the first byte of the payload will indicate
  31413. // the offset from the current position to the start of the
  31414. // section.
  31415. if (psi.payloadUnitStartIndicator) {
  31416. offset += payload[offset] + 1;
  31417. }
  31418. if (psi.type === 'pat') {
  31419. parsePat(payload.subarray(offset), psi);
  31420. } else {
  31421. parsePmt(payload.subarray(offset), psi);
  31422. }
  31423. };
  31424. parsePat = function parsePat(payload, pat) {
  31425. pat.section_number = payload[7]; // eslint-disable-line camelcase
  31426. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  31427. // skip the PSI header and parse the first PMT entry
  31428. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  31429. pat.pmtPid = self.pmtPid;
  31430. };
  31431. /**
  31432. * Parse out the relevant fields of a Program Map Table (PMT).
  31433. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  31434. * packet. The first byte in this array should be the table_id
  31435. * field.
  31436. * @param pmt {object} the object that should be decorated with
  31437. * fields parsed from the PMT.
  31438. */
  31439. parsePmt = function parsePmt(payload, pmt) {
  31440. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  31441. // take effect. We don't believe this should ever be the case
  31442. // for HLS but we'll ignore "forward" PMT declarations if we see
  31443. // them. Future PMT declarations have the current_next_indicator
  31444. // set to zero.
  31445. if (!(payload[5] & 0x01)) {
  31446. return;
  31447. } // overwrite any existing program map table
  31448. self.programMapTable = {
  31449. video: null,
  31450. audio: null,
  31451. 'timed-metadata': {}
  31452. }; // the mapping table ends at the end of the current section
  31453. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  31454. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  31455. // long the program info descriptors are
  31456. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  31457. offset = 12 + programInfoLength;
  31458. while (offset < tableEnd) {
  31459. var streamType = payload[offset];
  31460. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  31461. // TODO: should this be done for metadata too? for now maintain behavior of
  31462. // multiple metadata streams
  31463. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  31464. self.programMapTable.video = pid;
  31465. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  31466. self.programMapTable.audio = pid;
  31467. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  31468. // map pid to stream type for metadata streams
  31469. self.programMapTable['timed-metadata'][pid] = streamType;
  31470. } // move to the next table entry
  31471. // skip past the elementary stream descriptors, if present
  31472. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  31473. } // record the map on the packet as well
  31474. pmt.programMapTable = self.programMapTable;
  31475. };
  31476. /**
  31477. * Deliver a new MP2T packet to the next stream in the pipeline.
  31478. */
  31479. this.push = function (packet) {
  31480. var result = {},
  31481. offset = 4;
  31482. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  31483. result.pid = packet[1] & 0x1f;
  31484. result.pid <<= 8;
  31485. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  31486. // fifth byte of the TS packet header. The adaptation field is
  31487. // used to add stuffing to PES packets that don't fill a complete
  31488. // TS packet, and to specify some forms of timing and control data
  31489. // that we do not currently use.
  31490. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  31491. offset += packet[offset] + 1;
  31492. } // parse the rest of the packet based on the type
  31493. if (result.pid === 0) {
  31494. result.type = 'pat';
  31495. parsePsi(packet.subarray(offset), result);
  31496. this.trigger('data', result);
  31497. } else if (result.pid === this.pmtPid) {
  31498. result.type = 'pmt';
  31499. parsePsi(packet.subarray(offset), result);
  31500. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  31501. while (this.packetsWaitingForPmt.length) {
  31502. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  31503. }
  31504. } else if (this.programMapTable === undefined) {
  31505. // When we have not seen a PMT yet, defer further processing of
  31506. // PES packets until one has been parsed
  31507. this.packetsWaitingForPmt.push([packet, offset, result]);
  31508. } else {
  31509. this.processPes_(packet, offset, result);
  31510. }
  31511. };
  31512. this.processPes_ = function (packet, offset, result) {
  31513. // set the appropriate stream type
  31514. if (result.pid === this.programMapTable.video) {
  31515. result.streamType = streamTypes.H264_STREAM_TYPE;
  31516. } else if (result.pid === this.programMapTable.audio) {
  31517. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  31518. } else {
  31519. // if not video or audio, it is timed-metadata or unknown
  31520. // if unknown, streamType will be undefined
  31521. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  31522. }
  31523. result.type = 'pes';
  31524. result.data = packet.subarray(offset);
  31525. this.trigger('data', result);
  31526. };
  31527. };
  31528. _TransportParseStream.prototype = new stream();
  31529. _TransportParseStream.STREAM_TYPES = {
  31530. h264: 0x1b,
  31531. adts: 0x0f
  31532. };
  31533. /**
  31534. * Reconsistutes program elementary stream (PES) packets from parsed
  31535. * transport stream packets. That is, if you pipe an
  31536. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  31537. * events will be events which capture the bytes for individual PES
  31538. * packets plus relevant metadata that has been extracted from the
  31539. * container.
  31540. */
  31541. _ElementaryStream = function ElementaryStream() {
  31542. var self = this,
  31543. // PES packet fragments
  31544. video = {
  31545. data: [],
  31546. size: 0
  31547. },
  31548. audio = {
  31549. data: [],
  31550. size: 0
  31551. },
  31552. timedMetadata = {
  31553. data: [],
  31554. size: 0
  31555. },
  31556. parsePes = function parsePes(payload, pes) {
  31557. var ptsDtsFlags; // get the packet length, this will be 0 for video
  31558. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  31559. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  31560. // and a DTS value. Determine what combination of values is
  31561. // available to work with.
  31562. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  31563. // performs all bitwise operations on 32-bit integers but javascript
  31564. // supports a much greater range (52-bits) of integer using standard
  31565. // mathematical operations.
  31566. // We construct a 31-bit value using bitwise operators over the 31
  31567. // most significant bits and then multiply by 4 (equal to a left-shift
  31568. // of 2) before we add the final 2 least significant bits of the
  31569. // timestamp (equal to an OR.)
  31570. if (ptsDtsFlags & 0xC0) {
  31571. // the PTS and DTS are not written out directly. For information
  31572. // on how they are encoded, see
  31573. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  31574. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  31575. pes.pts *= 4; // Left shift by 2
  31576. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  31577. pes.dts = pes.pts;
  31578. if (ptsDtsFlags & 0x40) {
  31579. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  31580. pes.dts *= 4; // Left shift by 2
  31581. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  31582. }
  31583. } // the data section starts immediately after the PES header.
  31584. // pes_header_data_length specifies the number of header bytes
  31585. // that follow the last byte of the field.
  31586. pes.data = payload.subarray(9 + payload[8]);
  31587. },
  31588. /**
  31589. * Pass completely parsed PES packets to the next stream in the pipeline
  31590. **/
  31591. flushStream = function flushStream(stream, type, forceFlush) {
  31592. var packetData = new Uint8Array(stream.size),
  31593. event = {
  31594. type: type
  31595. },
  31596. i = 0,
  31597. offset = 0,
  31598. packetFlushable = false,
  31599. fragment; // do nothing if there is not enough buffered data for a complete
  31600. // PES header
  31601. if (!stream.data.length || stream.size < 9) {
  31602. return;
  31603. }
  31604. event.trackId = stream.data[0].pid; // reassemble the packet
  31605. for (i = 0; i < stream.data.length; i++) {
  31606. fragment = stream.data[i];
  31607. packetData.set(fragment.data, offset);
  31608. offset += fragment.data.byteLength;
  31609. } // parse assembled packet's PES header
  31610. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  31611. // check that there is enough stream data to fill the packet
  31612. packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
  31613. if (forceFlush || packetFlushable) {
  31614. stream.size = 0;
  31615. stream.data.length = 0;
  31616. } // only emit packets that are complete. this is to avoid assembling
  31617. // incomplete PES packets due to poor segmentation
  31618. if (packetFlushable) {
  31619. self.trigger('data', event);
  31620. }
  31621. };
  31622. _ElementaryStream.prototype.init.call(this);
  31623. /**
  31624. * Identifies M2TS packet types and parses PES packets using metadata
  31625. * parsed from the PMT
  31626. **/
  31627. this.push = function (data) {
  31628. ({
  31629. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  31630. // have any meaningful metadata
  31631. },
  31632. pes: function pes() {
  31633. var stream, streamType;
  31634. switch (data.streamType) {
  31635. case streamTypes.H264_STREAM_TYPE:
  31636. case streamTypes.H264_STREAM_TYPE:
  31637. stream = video;
  31638. streamType = 'video';
  31639. break;
  31640. case streamTypes.ADTS_STREAM_TYPE:
  31641. stream = audio;
  31642. streamType = 'audio';
  31643. break;
  31644. case streamTypes.METADATA_STREAM_TYPE:
  31645. stream = timedMetadata;
  31646. streamType = 'timed-metadata';
  31647. break;
  31648. default:
  31649. // ignore unknown stream types
  31650. return;
  31651. } // if a new packet is starting, we can flush the completed
  31652. // packet
  31653. if (data.payloadUnitStartIndicator) {
  31654. flushStream(stream, streamType, true);
  31655. } // buffer this fragment until we are sure we've received the
  31656. // complete payload
  31657. stream.data.push(data);
  31658. stream.size += data.data.byteLength;
  31659. },
  31660. pmt: function pmt() {
  31661. var event = {
  31662. type: 'metadata',
  31663. tracks: []
  31664. },
  31665. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  31666. if (programMapTable.video !== null) {
  31667. event.tracks.push({
  31668. timelineStartInfo: {
  31669. baseMediaDecodeTime: 0
  31670. },
  31671. id: +programMapTable.video,
  31672. codec: 'avc',
  31673. type: 'video'
  31674. });
  31675. }
  31676. if (programMapTable.audio !== null) {
  31677. event.tracks.push({
  31678. timelineStartInfo: {
  31679. baseMediaDecodeTime: 0
  31680. },
  31681. id: +programMapTable.audio,
  31682. codec: 'adts',
  31683. type: 'audio'
  31684. });
  31685. }
  31686. self.trigger('data', event);
  31687. }
  31688. })[data.type]();
  31689. };
  31690. /**
  31691. * Flush any remaining input. Video PES packets may be of variable
  31692. * length. Normally, the start of a new video packet can trigger the
  31693. * finalization of the previous packet. That is not possible if no
  31694. * more video is forthcoming, however. In that case, some other
  31695. * mechanism (like the end of the file) has to be employed. When it is
  31696. * clear that no additional data is forthcoming, calling this method
  31697. * will flush the buffered packets.
  31698. */
  31699. this.flush = function () {
  31700. // !!THIS ORDER IS IMPORTANT!!
  31701. // video first then audio
  31702. flushStream(video, 'video');
  31703. flushStream(audio, 'audio');
  31704. flushStream(timedMetadata, 'timed-metadata');
  31705. this.trigger('done');
  31706. };
  31707. };
  31708. _ElementaryStream.prototype = new stream();
  31709. var m2ts = {
  31710. PAT_PID: 0x0000,
  31711. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  31712. TransportPacketStream: _TransportPacketStream,
  31713. TransportParseStream: _TransportParseStream,
  31714. ElementaryStream: _ElementaryStream,
  31715. TimestampRolloverStream: TimestampRolloverStream$1,
  31716. CaptionStream: captionStream.CaptionStream,
  31717. Cea608Stream: captionStream.Cea608Stream,
  31718. MetadataStream: metadataStream
  31719. };
  31720. for (var type$1 in streamTypes) {
  31721. if (streamTypes.hasOwnProperty(type$1)) {
  31722. m2ts[type$1] = streamTypes[type$1];
  31723. }
  31724. }
  31725. var m2ts_1 = m2ts;
  31726. var _AdtsStream;
  31727. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  31728. /*
  31729. * Accepts a ElementaryStream and emits data events with parsed
  31730. * AAC Audio Frames of the individual packets. Input audio in ADTS
  31731. * format is unpacked and re-emitted as AAC frames.
  31732. *
  31733. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  31734. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  31735. */
  31736. _AdtsStream = function AdtsStream() {
  31737. var buffer;
  31738. _AdtsStream.prototype.init.call(this);
  31739. this.push = function (packet) {
  31740. var i = 0,
  31741. frameNum = 0,
  31742. frameLength,
  31743. protectionSkipBytes,
  31744. frameEnd,
  31745. oldBuffer,
  31746. sampleCount,
  31747. adtsFrameDuration;
  31748. if (packet.type !== 'audio') {
  31749. // ignore non-audio data
  31750. return;
  31751. } // Prepend any data in the buffer to the input data so that we can parse
  31752. // aac frames the cross a PES packet boundary
  31753. if (buffer) {
  31754. oldBuffer = buffer;
  31755. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  31756. buffer.set(oldBuffer);
  31757. buffer.set(packet.data, oldBuffer.byteLength);
  31758. } else {
  31759. buffer = packet.data;
  31760. } // unpack any ADTS frames which have been fully received
  31761. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  31762. while (i + 5 < buffer.length) {
  31763. // Loook for the start of an ADTS header..
  31764. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  31765. // If a valid header was not found, jump one forward and attempt to
  31766. // find a valid ADTS header starting at the next byte
  31767. i++;
  31768. continue;
  31769. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  31770. // end of the ADTS header
  31771. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  31772. // end of the sync sequence
  31773. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  31774. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  31775. adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
  31776. frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
  31777. // and wait for more data
  31778. if (buffer.byteLength < frameEnd) {
  31779. return;
  31780. } // Otherwise, deliver the complete AAC frame
  31781. this.trigger('data', {
  31782. pts: packet.pts + frameNum * adtsFrameDuration,
  31783. dts: packet.dts + frameNum * adtsFrameDuration,
  31784. sampleCount: sampleCount,
  31785. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  31786. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  31787. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  31788. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  31789. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  31790. samplesize: 16,
  31791. data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
  31792. }); // If the buffer is empty, clear it and return
  31793. if (buffer.byteLength === frameEnd) {
  31794. buffer = undefined;
  31795. return;
  31796. }
  31797. frameNum++; // Remove the finished frame from the buffer and start the process again
  31798. buffer = buffer.subarray(frameEnd);
  31799. }
  31800. };
  31801. this.flush = function () {
  31802. this.trigger('done');
  31803. };
  31804. };
  31805. _AdtsStream.prototype = new stream();
  31806. var adts = _AdtsStream;
  31807. /**
  31808. * mux.js
  31809. *
  31810. * Copyright (c) Brightcove
  31811. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  31812. */
  31813. var ExpGolomb;
  31814. /**
  31815. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  31816. * scheme used by h264.
  31817. */
  31818. ExpGolomb = function ExpGolomb(workingData) {
  31819. var // the number of bytes left to examine in workingData
  31820. workingBytesAvailable = workingData.byteLength,
  31821. // the current word being examined
  31822. workingWord = 0,
  31823. // :uint
  31824. // the number of bits left to examine in the current word
  31825. workingBitsAvailable = 0; // :uint;
  31826. // ():uint
  31827. this.length = function () {
  31828. return 8 * workingBytesAvailable;
  31829. }; // ():uint
  31830. this.bitsAvailable = function () {
  31831. return 8 * workingBytesAvailable + workingBitsAvailable;
  31832. }; // ():void
  31833. this.loadWord = function () {
  31834. var position = workingData.byteLength - workingBytesAvailable,
  31835. workingBytes = new Uint8Array(4),
  31836. availableBytes = Math.min(4, workingBytesAvailable);
  31837. if (availableBytes === 0) {
  31838. throw new Error('no bytes available');
  31839. }
  31840. workingBytes.set(workingData.subarray(position, position + availableBytes));
  31841. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  31842. workingBitsAvailable = availableBytes * 8;
  31843. workingBytesAvailable -= availableBytes;
  31844. }; // (count:int):void
  31845. this.skipBits = function (count) {
  31846. var skipBytes; // :int
  31847. if (workingBitsAvailable > count) {
  31848. workingWord <<= count;
  31849. workingBitsAvailable -= count;
  31850. } else {
  31851. count -= workingBitsAvailable;
  31852. skipBytes = Math.floor(count / 8);
  31853. count -= skipBytes * 8;
  31854. workingBytesAvailable -= skipBytes;
  31855. this.loadWord();
  31856. workingWord <<= count;
  31857. workingBitsAvailable -= count;
  31858. }
  31859. }; // (size:int):uint
  31860. this.readBits = function (size) {
  31861. var bits = Math.min(workingBitsAvailable, size),
  31862. // :uint
  31863. valu = workingWord >>> 32 - bits; // :uint
  31864. // if size > 31, handle error
  31865. workingBitsAvailable -= bits;
  31866. if (workingBitsAvailable > 0) {
  31867. workingWord <<= bits;
  31868. } else if (workingBytesAvailable > 0) {
  31869. this.loadWord();
  31870. }
  31871. bits = size - bits;
  31872. if (bits > 0) {
  31873. return valu << bits | this.readBits(bits);
  31874. }
  31875. return valu;
  31876. }; // ():uint
  31877. this.skipLeadingZeros = function () {
  31878. var leadingZeroCount; // :uint
  31879. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  31880. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  31881. // the first bit of working word is 1
  31882. workingWord <<= leadingZeroCount;
  31883. workingBitsAvailable -= leadingZeroCount;
  31884. return leadingZeroCount;
  31885. }
  31886. } // we exhausted workingWord and still have not found a 1
  31887. this.loadWord();
  31888. return leadingZeroCount + this.skipLeadingZeros();
  31889. }; // ():void
  31890. this.skipUnsignedExpGolomb = function () {
  31891. this.skipBits(1 + this.skipLeadingZeros());
  31892. }; // ():void
  31893. this.skipExpGolomb = function () {
  31894. this.skipBits(1 + this.skipLeadingZeros());
  31895. }; // ():uint
  31896. this.readUnsignedExpGolomb = function () {
  31897. var clz = this.skipLeadingZeros(); // :uint
  31898. return this.readBits(clz + 1) - 1;
  31899. }; // ():int
  31900. this.readExpGolomb = function () {
  31901. var valu = this.readUnsignedExpGolomb(); // :int
  31902. if (0x01 & valu) {
  31903. // the number is odd if the low order bit is set
  31904. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  31905. }
  31906. return -1 * (valu >>> 1); // divide by two then make it negative
  31907. }; // Some convenience functions
  31908. // :Boolean
  31909. this.readBoolean = function () {
  31910. return this.readBits(1) === 1;
  31911. }; // ():int
  31912. this.readUnsignedByte = function () {
  31913. return this.readBits(8);
  31914. };
  31915. this.loadWord();
  31916. };
  31917. var expGolomb = ExpGolomb;
  31918. var _H264Stream, _NalByteStream;
  31919. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  31920. /**
  31921. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  31922. */
  31923. _NalByteStream = function NalByteStream() {
  31924. var syncPoint = 0,
  31925. i,
  31926. buffer;
  31927. _NalByteStream.prototype.init.call(this);
  31928. /*
  31929. * Scans a byte stream and triggers a data event with the NAL units found.
  31930. * @param {Object} data Event received from H264Stream
  31931. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  31932. *
  31933. * @see H264Stream.push
  31934. */
  31935. this.push = function (data) {
  31936. var swapBuffer;
  31937. if (!buffer) {
  31938. buffer = data.data;
  31939. } else {
  31940. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  31941. swapBuffer.set(buffer);
  31942. swapBuffer.set(data.data, buffer.byteLength);
  31943. buffer = swapBuffer;
  31944. } // Rec. ITU-T H.264, Annex B
  31945. // scan for NAL unit boundaries
  31946. // a match looks like this:
  31947. // 0 0 1 .. NAL .. 0 0 1
  31948. // ^ sync point ^ i
  31949. // or this:
  31950. // 0 0 1 .. NAL .. 0 0 0
  31951. // ^ sync point ^ i
  31952. // advance the sync point to a NAL start, if necessary
  31953. for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
  31954. if (buffer[syncPoint + 2] === 1) {
  31955. // the sync point is properly aligned
  31956. i = syncPoint + 5;
  31957. break;
  31958. }
  31959. }
  31960. while (i < buffer.byteLength) {
  31961. // look at the current byte to determine if we've hit the end of
  31962. // a NAL unit boundary
  31963. switch (buffer[i]) {
  31964. case 0:
  31965. // skip past non-sync sequences
  31966. if (buffer[i - 1] !== 0) {
  31967. i += 2;
  31968. break;
  31969. } else if (buffer[i - 2] !== 0) {
  31970. i++;
  31971. break;
  31972. } // deliver the NAL unit if it isn't empty
  31973. if (syncPoint + 3 !== i - 2) {
  31974. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  31975. } // drop trailing zeroes
  31976. do {
  31977. i++;
  31978. } while (buffer[i] !== 1 && i < buffer.length);
  31979. syncPoint = i - 2;
  31980. i += 3;
  31981. break;
  31982. case 1:
  31983. // skip past non-sync sequences
  31984. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  31985. i += 3;
  31986. break;
  31987. } // deliver the NAL unit
  31988. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  31989. syncPoint = i - 2;
  31990. i += 3;
  31991. break;
  31992. default:
  31993. // the current byte isn't a one or zero, so it cannot be part
  31994. // of a sync sequence
  31995. i += 3;
  31996. break;
  31997. }
  31998. } // filter out the NAL units that were delivered
  31999. buffer = buffer.subarray(syncPoint);
  32000. i -= syncPoint;
  32001. syncPoint = 0;
  32002. };
  32003. this.flush = function () {
  32004. // deliver the last buffered NAL unit
  32005. if (buffer && buffer.byteLength > 3) {
  32006. this.trigger('data', buffer.subarray(syncPoint + 3));
  32007. } // reset the stream state
  32008. buffer = null;
  32009. syncPoint = 0;
  32010. this.trigger('done');
  32011. };
  32012. };
  32013. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  32014. // see Recommendation ITU-T H.264 (4/2013),
  32015. // 7.3.2.1.1 Sequence parameter set data syntax
  32016. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  32017. 100: true,
  32018. 110: true,
  32019. 122: true,
  32020. 244: true,
  32021. 44: true,
  32022. 83: true,
  32023. 86: true,
  32024. 118: true,
  32025. 128: true,
  32026. 138: true,
  32027. 139: true,
  32028. 134: true
  32029. };
  32030. /**
  32031. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  32032. * events.
  32033. */
  32034. _H264Stream = function H264Stream() {
  32035. var nalByteStream = new _NalByteStream(),
  32036. self,
  32037. trackId,
  32038. currentPts,
  32039. currentDts,
  32040. discardEmulationPreventionBytes,
  32041. readSequenceParameterSet,
  32042. skipScalingList;
  32043. _H264Stream.prototype.init.call(this);
  32044. self = this;
  32045. /*
  32046. * Pushes a packet from a stream onto the NalByteStream
  32047. *
  32048. * @param {Object} packet - A packet received from a stream
  32049. * @param {Uint8Array} packet.data - The raw bytes of the packet
  32050. * @param {Number} packet.dts - Decode timestamp of the packet
  32051. * @param {Number} packet.pts - Presentation timestamp of the packet
  32052. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  32053. * @param {('video'|'audio')} packet.type - The type of packet
  32054. *
  32055. */
  32056. this.push = function (packet) {
  32057. if (packet.type !== 'video') {
  32058. return;
  32059. }
  32060. trackId = packet.trackId;
  32061. currentPts = packet.pts;
  32062. currentDts = packet.dts;
  32063. nalByteStream.push(packet);
  32064. };
  32065. /*
  32066. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  32067. * for the NALUs to the next stream component.
  32068. * Also, preprocess caption and sequence parameter NALUs.
  32069. *
  32070. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  32071. * @see NalByteStream.push
  32072. */
  32073. nalByteStream.on('data', function (data) {
  32074. var event = {
  32075. trackId: trackId,
  32076. pts: currentPts,
  32077. dts: currentDts,
  32078. data: data
  32079. };
  32080. switch (data[0] & 0x1f) {
  32081. case 0x05:
  32082. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  32083. break;
  32084. case 0x06:
  32085. event.nalUnitType = 'sei_rbsp';
  32086. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  32087. break;
  32088. case 0x07:
  32089. event.nalUnitType = 'seq_parameter_set_rbsp';
  32090. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  32091. event.config = readSequenceParameterSet(event.escapedRBSP);
  32092. break;
  32093. case 0x08:
  32094. event.nalUnitType = 'pic_parameter_set_rbsp';
  32095. break;
  32096. case 0x09:
  32097. event.nalUnitType = 'access_unit_delimiter_rbsp';
  32098. break;
  32099. default:
  32100. break;
  32101. } // This triggers data on the H264Stream
  32102. self.trigger('data', event);
  32103. });
  32104. nalByteStream.on('done', function () {
  32105. self.trigger('done');
  32106. });
  32107. this.flush = function () {
  32108. nalByteStream.flush();
  32109. };
  32110. /**
  32111. * Advance the ExpGolomb decoder past a scaling list. The scaling
  32112. * list is optionally transmitted as part of a sequence parameter
  32113. * set and is not relevant to transmuxing.
  32114. * @param count {number} the number of entries in this scaling list
  32115. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  32116. * start of a scaling list
  32117. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  32118. */
  32119. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  32120. var lastScale = 8,
  32121. nextScale = 8,
  32122. j,
  32123. deltaScale;
  32124. for (j = 0; j < count; j++) {
  32125. if (nextScale !== 0) {
  32126. deltaScale = expGolombDecoder.readExpGolomb();
  32127. nextScale = (lastScale + deltaScale + 256) % 256;
  32128. }
  32129. lastScale = nextScale === 0 ? lastScale : nextScale;
  32130. }
  32131. };
  32132. /**
  32133. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  32134. * Sequence Payload"
  32135. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  32136. * unit
  32137. * @return {Uint8Array} the RBSP without any Emulation
  32138. * Prevention Bytes
  32139. */
  32140. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  32141. var length = data.byteLength,
  32142. emulationPreventionBytesPositions = [],
  32143. i = 1,
  32144. newLength,
  32145. newData; // Find all `Emulation Prevention Bytes`
  32146. while (i < length - 2) {
  32147. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  32148. emulationPreventionBytesPositions.push(i + 2);
  32149. i += 2;
  32150. } else {
  32151. i++;
  32152. }
  32153. } // If no Emulation Prevention Bytes were found just return the original
  32154. // array
  32155. if (emulationPreventionBytesPositions.length === 0) {
  32156. return data;
  32157. } // Create a new array to hold the NAL unit data
  32158. newLength = length - emulationPreventionBytesPositions.length;
  32159. newData = new Uint8Array(newLength);
  32160. var sourceIndex = 0;
  32161. for (i = 0; i < newLength; sourceIndex++, i++) {
  32162. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  32163. // Skip this byte
  32164. sourceIndex++; // Remove this position index
  32165. emulationPreventionBytesPositions.shift();
  32166. }
  32167. newData[i] = data[sourceIndex];
  32168. }
  32169. return newData;
  32170. };
  32171. /**
  32172. * Read a sequence parameter set and return some interesting video
  32173. * properties. A sequence parameter set is the H264 metadata that
  32174. * describes the properties of upcoming video frames.
  32175. * @param data {Uint8Array} the bytes of a sequence parameter set
  32176. * @return {object} an object with configuration parsed from the
  32177. * sequence parameter set, including the dimensions of the
  32178. * associated video frames.
  32179. */
  32180. readSequenceParameterSet = function readSequenceParameterSet(data) {
  32181. var frameCropLeftOffset = 0,
  32182. frameCropRightOffset = 0,
  32183. frameCropTopOffset = 0,
  32184. frameCropBottomOffset = 0,
  32185. sarScale = 1,
  32186. expGolombDecoder,
  32187. profileIdc,
  32188. levelIdc,
  32189. profileCompatibility,
  32190. chromaFormatIdc,
  32191. picOrderCntType,
  32192. numRefFramesInPicOrderCntCycle,
  32193. picWidthInMbsMinus1,
  32194. picHeightInMapUnitsMinus1,
  32195. frameMbsOnlyFlag,
  32196. scalingListCount,
  32197. sarRatio,
  32198. aspectRatioIdc,
  32199. i;
  32200. expGolombDecoder = new expGolomb(data);
  32201. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  32202. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  32203. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  32204. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  32205. // some profiles have more optional data we don't need
  32206. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  32207. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  32208. if (chromaFormatIdc === 3) {
  32209. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  32210. }
  32211. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  32212. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  32213. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  32214. if (expGolombDecoder.readBoolean()) {
  32215. // seq_scaling_matrix_present_flag
  32216. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  32217. for (i = 0; i < scalingListCount; i++) {
  32218. if (expGolombDecoder.readBoolean()) {
  32219. // seq_scaling_list_present_flag[ i ]
  32220. if (i < 6) {
  32221. skipScalingList(16, expGolombDecoder);
  32222. } else {
  32223. skipScalingList(64, expGolombDecoder);
  32224. }
  32225. }
  32226. }
  32227. }
  32228. }
  32229. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  32230. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  32231. if (picOrderCntType === 0) {
  32232. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  32233. } else if (picOrderCntType === 1) {
  32234. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  32235. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  32236. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  32237. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  32238. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  32239. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  32240. }
  32241. }
  32242. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  32243. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  32244. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  32245. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  32246. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  32247. if (frameMbsOnlyFlag === 0) {
  32248. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  32249. }
  32250. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  32251. if (expGolombDecoder.readBoolean()) {
  32252. // frame_cropping_flag
  32253. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  32254. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  32255. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  32256. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  32257. }
  32258. if (expGolombDecoder.readBoolean()) {
  32259. // vui_parameters_present_flag
  32260. if (expGolombDecoder.readBoolean()) {
  32261. // aspect_ratio_info_present_flag
  32262. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  32263. switch (aspectRatioIdc) {
  32264. case 1:
  32265. sarRatio = [1, 1];
  32266. break;
  32267. case 2:
  32268. sarRatio = [12, 11];
  32269. break;
  32270. case 3:
  32271. sarRatio = [10, 11];
  32272. break;
  32273. case 4:
  32274. sarRatio = [16, 11];
  32275. break;
  32276. case 5:
  32277. sarRatio = [40, 33];
  32278. break;
  32279. case 6:
  32280. sarRatio = [24, 11];
  32281. break;
  32282. case 7:
  32283. sarRatio = [20, 11];
  32284. break;
  32285. case 8:
  32286. sarRatio = [32, 11];
  32287. break;
  32288. case 9:
  32289. sarRatio = [80, 33];
  32290. break;
  32291. case 10:
  32292. sarRatio = [18, 11];
  32293. break;
  32294. case 11:
  32295. sarRatio = [15, 11];
  32296. break;
  32297. case 12:
  32298. sarRatio = [64, 33];
  32299. break;
  32300. case 13:
  32301. sarRatio = [160, 99];
  32302. break;
  32303. case 14:
  32304. sarRatio = [4, 3];
  32305. break;
  32306. case 15:
  32307. sarRatio = [3, 2];
  32308. break;
  32309. case 16:
  32310. sarRatio = [2, 1];
  32311. break;
  32312. case 255:
  32313. {
  32314. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  32315. break;
  32316. }
  32317. }
  32318. if (sarRatio) {
  32319. sarScale = sarRatio[0] / sarRatio[1];
  32320. }
  32321. }
  32322. }
  32323. return {
  32324. profileIdc: profileIdc,
  32325. levelIdc: levelIdc,
  32326. profileCompatibility: profileCompatibility,
  32327. width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  32328. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
  32329. };
  32330. };
  32331. };
  32332. _H264Stream.prototype = new stream();
  32333. var h264 = {
  32334. H264Stream: _H264Stream,
  32335. NalByteStream: _NalByteStream
  32336. };
  32337. /**
  32338. * mux.js
  32339. *
  32340. * Copyright (c) Brightcove
  32341. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  32342. *
  32343. * Utilities to detect basic properties and metadata about Aac data.
  32344. */
  32345. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  32346. var isLikelyAacData = function isLikelyAacData(data) {
  32347. if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
  32348. return true;
  32349. }
  32350. return false;
  32351. };
  32352. var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  32353. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  32354. }; // return a percent-encoded representation of the specified byte range
  32355. // @see http://en.wikipedia.org/wiki/Percent-encoding
  32356. var percentEncode$1 = function percentEncode(bytes, start, end) {
  32357. var i,
  32358. result = '';
  32359. for (i = start; i < end; i++) {
  32360. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  32361. }
  32362. return result;
  32363. }; // return the string representation of the specified byte range,
  32364. // interpreted as ISO-8859-1.
  32365. var parseIso88591$1 = function parseIso88591(bytes, start, end) {
  32366. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  32367. };
  32368. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  32369. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  32370. flags = header[byteIndex + 5],
  32371. footerPresent = (flags & 16) >> 4;
  32372. if (footerPresent) {
  32373. return returnSize + 20;
  32374. }
  32375. return returnSize + 10;
  32376. };
  32377. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  32378. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  32379. middle = header[byteIndex + 4] << 3,
  32380. highTwo = header[byteIndex + 3] & 0x3 << 11;
  32381. return highTwo | middle | lowThree;
  32382. };
  32383. var parseType$2 = function parseType(header, byteIndex) {
  32384. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  32385. return 'timed-metadata';
  32386. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  32387. return 'audio';
  32388. }
  32389. return null;
  32390. };
  32391. var parseSampleRate = function parseSampleRate(packet) {
  32392. var i = 0;
  32393. while (i + 5 < packet.length) {
  32394. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  32395. // If a valid header was not found, jump one forward and attempt to
  32396. // find a valid ADTS header starting at the next byte
  32397. i++;
  32398. continue;
  32399. }
  32400. return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
  32401. }
  32402. return null;
  32403. };
  32404. var parseAacTimestamp = function parseAacTimestamp(packet) {
  32405. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  32406. frameStart = 10;
  32407. if (packet[5] & 0x40) {
  32408. // advance the frame start past the extended header
  32409. frameStart += 4; // header size field
  32410. frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
  32411. } // parse one or more ID3 frames
  32412. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  32413. do {
  32414. // determine the number of bytes in this frame
  32415. frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
  32416. if (frameSize < 1) {
  32417. return null;
  32418. }
  32419. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  32420. if (frameHeader === 'PRIV') {
  32421. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  32422. for (var i = 0; i < frame.byteLength; i++) {
  32423. if (frame[i] === 0) {
  32424. var owner = parseIso88591$1(frame, 0, i);
  32425. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  32426. var d = frame.subarray(i + 1);
  32427. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  32428. size *= 4;
  32429. size += d[7] & 0x03;
  32430. return size;
  32431. }
  32432. break;
  32433. }
  32434. }
  32435. }
  32436. frameStart += 10; // advance past the frame header
  32437. frameStart += frameSize; // advance past the frame body
  32438. } while (frameStart < packet.byteLength);
  32439. return null;
  32440. };
  32441. var utils = {
  32442. isLikelyAacData: isLikelyAacData,
  32443. parseId3TagSize: parseId3TagSize,
  32444. parseAdtsSize: parseAdtsSize,
  32445. parseType: parseType$2,
  32446. parseSampleRate: parseSampleRate,
  32447. parseAacTimestamp: parseAacTimestamp
  32448. };
  32449. var _AacStream;
  32450. /**
  32451. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  32452. */
  32453. _AacStream = function AacStream() {
  32454. var everything = new Uint8Array(),
  32455. timeStamp = 0;
  32456. _AacStream.prototype.init.call(this);
  32457. this.setTimestamp = function (timestamp) {
  32458. timeStamp = timestamp;
  32459. };
  32460. this.push = function (bytes) {
  32461. var frameSize = 0,
  32462. byteIndex = 0,
  32463. bytesLeft,
  32464. chunk,
  32465. packet,
  32466. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  32467. // bytes that were pushed in
  32468. if (everything.length) {
  32469. tempLength = everything.length;
  32470. everything = new Uint8Array(bytes.byteLength + tempLength);
  32471. everything.set(everything.subarray(0, tempLength));
  32472. everything.set(bytes, tempLength);
  32473. } else {
  32474. everything = bytes;
  32475. }
  32476. while (everything.length - byteIndex >= 3) {
  32477. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  32478. // Exit early because we don't have enough to parse
  32479. // the ID3 tag header
  32480. if (everything.length - byteIndex < 10) {
  32481. break;
  32482. } // check framesize
  32483. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  32484. // to emit a full packet
  32485. // Add to byteIndex to support multiple ID3 tags in sequence
  32486. if (byteIndex + frameSize > everything.length) {
  32487. break;
  32488. }
  32489. chunk = {
  32490. type: 'timed-metadata',
  32491. data: everything.subarray(byteIndex, byteIndex + frameSize)
  32492. };
  32493. this.trigger('data', chunk);
  32494. byteIndex += frameSize;
  32495. continue;
  32496. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  32497. // Exit early because we don't have enough to parse
  32498. // the ADTS frame header
  32499. if (everything.length - byteIndex < 7) {
  32500. break;
  32501. }
  32502. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  32503. // to emit a full packet
  32504. if (byteIndex + frameSize > everything.length) {
  32505. break;
  32506. }
  32507. packet = {
  32508. type: 'audio',
  32509. data: everything.subarray(byteIndex, byteIndex + frameSize),
  32510. pts: timeStamp,
  32511. dts: timeStamp
  32512. };
  32513. this.trigger('data', packet);
  32514. byteIndex += frameSize;
  32515. continue;
  32516. }
  32517. byteIndex++;
  32518. }
  32519. bytesLeft = everything.length - byteIndex;
  32520. if (bytesLeft > 0) {
  32521. everything = everything.subarray(byteIndex);
  32522. } else {
  32523. everything = new Uint8Array();
  32524. }
  32525. };
  32526. };
  32527. _AacStream.prototype = new stream();
  32528. var aac = _AacStream;
  32529. var H264Stream = h264.H264Stream;
  32530. var isLikelyAacData$1 = utils.isLikelyAacData; // constants
  32531. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  32532. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
  32533. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  32534. /**
  32535. * Compare two arrays (even typed) for same-ness
  32536. */
  32537. var arrayEquals = function arrayEquals(a, b) {
  32538. var i;
  32539. if (a.length !== b.length) {
  32540. return false;
  32541. } // compare the value of each element in the array
  32542. for (i = 0; i < a.length; i++) {
  32543. if (a[i] !== b[i]) {
  32544. return false;
  32545. }
  32546. }
  32547. return true;
  32548. };
  32549. var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  32550. var ptsOffsetFromDts = startPts - startDts,
  32551. decodeDuration = endDts - startDts,
  32552. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  32553. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  32554. // In order to provide relevant values for the player times, base timing info on the
  32555. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  32556. return {
  32557. start: {
  32558. dts: baseMediaDecodeTime,
  32559. pts: baseMediaDecodeTime + ptsOffsetFromDts
  32560. },
  32561. end: {
  32562. dts: baseMediaDecodeTime + decodeDuration,
  32563. pts: baseMediaDecodeTime + presentationDuration
  32564. },
  32565. prependedContentDuration: prependedContentDuration,
  32566. baseMediaDecodeTime: baseMediaDecodeTime
  32567. };
  32568. };
  32569. /**
  32570. * Constructs a single-track, ISO BMFF media segment from AAC data
  32571. * events. The output of this stream can be fed to a SourceBuffer
  32572. * configured with a suitable initialization segment.
  32573. * @param track {object} track metadata configuration
  32574. * @param options {object} transmuxer options object
  32575. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  32576. * in the source; false to adjust the first segment to start at 0.
  32577. */
  32578. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  32579. var adtsFrames = [],
  32580. sequenceNumber = 0,
  32581. earliestAllowedDts = 0,
  32582. audioAppendStartTs = 0,
  32583. videoBaseMediaDecodeTime = Infinity;
  32584. options = options || {};
  32585. _AudioSegmentStream.prototype.init.call(this);
  32586. this.push = function (data) {
  32587. trackDecodeInfo.collectDtsInfo(track, data);
  32588. if (track) {
  32589. AUDIO_PROPERTIES.forEach(function (prop) {
  32590. track[prop] = data[prop];
  32591. });
  32592. } // buffer audio data until end() is called
  32593. adtsFrames.push(data);
  32594. };
  32595. this.setEarliestDts = function (earliestDts) {
  32596. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  32597. };
  32598. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  32599. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  32600. };
  32601. this.setAudioAppendStart = function (timestamp) {
  32602. audioAppendStartTs = timestamp;
  32603. };
  32604. this.flush = function () {
  32605. var frames, moof, mdat, boxes; // return early if no audio data has been observed
  32606. if (adtsFrames.length === 0) {
  32607. this.trigger('done', 'AudioSegmentStream');
  32608. return;
  32609. }
  32610. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  32611. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  32612. audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  32613. // samples (that is, adts frames) in the audio data
  32614. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  32615. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  32616. adtsFrames = [];
  32617. moof = mp4Generator.moof(sequenceNumber, [track]);
  32618. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  32619. sequenceNumber++;
  32620. boxes.set(moof);
  32621. boxes.set(mdat, moof.byteLength);
  32622. trackDecodeInfo.clearDtsInfo(track);
  32623. this.trigger('data', {
  32624. track: track,
  32625. boxes: boxes
  32626. });
  32627. this.trigger('done', 'AudioSegmentStream');
  32628. };
  32629. };
  32630. _AudioSegmentStream.prototype = new stream();
  32631. /**
  32632. * Constructs a single-track, ISO BMFF media segment from H264 data
  32633. * events. The output of this stream can be fed to a SourceBuffer
  32634. * configured with a suitable initialization segment.
  32635. * @param track {object} track metadata configuration
  32636. * @param options {object} transmuxer options object
  32637. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  32638. * gopsToAlignWith list when attempting to align gop pts
  32639. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  32640. * in the source; false to adjust the first segment to start at 0.
  32641. */
  32642. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  32643. var sequenceNumber = 0,
  32644. nalUnits = [],
  32645. gopsToAlignWith = [],
  32646. config,
  32647. pps;
  32648. options = options || {};
  32649. _VideoSegmentStream.prototype.init.call(this);
  32650. delete track.minPTS;
  32651. this.gopCache_ = [];
  32652. /**
  32653. * Constructs a ISO BMFF segment given H264 nalUnits
  32654. * @param {Object} nalUnit A data event representing a nalUnit
  32655. * @param {String} nalUnit.nalUnitType
  32656. * @param {Object} nalUnit.config Properties for a mp4 track
  32657. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  32658. * @see lib/codecs/h264.js
  32659. **/
  32660. this.push = function (nalUnit) {
  32661. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  32662. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  32663. config = nalUnit.config;
  32664. track.sps = [nalUnit.data];
  32665. VIDEO_PROPERTIES.forEach(function (prop) {
  32666. track[prop] = config[prop];
  32667. }, this);
  32668. }
  32669. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  32670. pps = nalUnit.data;
  32671. track.pps = [nalUnit.data];
  32672. } // buffer video until flush() is called
  32673. nalUnits.push(nalUnit);
  32674. };
  32675. /**
  32676. * Pass constructed ISO BMFF track and boxes on to the
  32677. * next stream in the pipeline
  32678. **/
  32679. this.flush = function () {
  32680. var frames,
  32681. gopForFusion,
  32682. gops,
  32683. moof,
  32684. mdat,
  32685. boxes,
  32686. prependedContentDuration = 0,
  32687. firstGop,
  32688. lastGop; // Throw away nalUnits at the start of the byte stream until
  32689. // we find the first AUD
  32690. while (nalUnits.length) {
  32691. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  32692. break;
  32693. }
  32694. nalUnits.shift();
  32695. } // Return early if no video data has been observed
  32696. if (nalUnits.length === 0) {
  32697. this.resetStream_();
  32698. this.trigger('done', 'VideoSegmentStream');
  32699. return;
  32700. } // Organize the raw nal-units into arrays that represent
  32701. // higher-level constructs such as frames and gops
  32702. // (group-of-pictures)
  32703. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  32704. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  32705. // a problem since MSE (on Chrome) requires a leading keyframe.
  32706. //
  32707. // We have two approaches to repairing this situation:
  32708. // 1) GOP-FUSION:
  32709. // This is where we keep track of the GOPS (group-of-pictures)
  32710. // from previous fragments and attempt to find one that we can
  32711. // prepend to the current fragment in order to create a valid
  32712. // fragment.
  32713. // 2) KEYFRAME-PULLING:
  32714. // Here we search for the first keyframe in the fragment and
  32715. // throw away all the frames between the start of the fragment
  32716. // and that keyframe. We then extend the duration and pull the
  32717. // PTS of the keyframe forward so that it covers the time range
  32718. // of the frames that were disposed of.
  32719. //
  32720. // #1 is far prefereable over #2 which can cause "stuttering" but
  32721. // requires more things to be just right.
  32722. if (!gops[0][0].keyFrame) {
  32723. // Search for a gop for fusion from our gopCache
  32724. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  32725. if (gopForFusion) {
  32726. // in order to provide more accurate timing information about the segment, save
  32727. // the number of seconds prepended to the original segment due to GOP fusion
  32728. prependedContentDuration = gopForFusion.duration;
  32729. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  32730. // new gop at the beginning
  32731. gops.byteLength += gopForFusion.byteLength;
  32732. gops.nalCount += gopForFusion.nalCount;
  32733. gops.pts = gopForFusion.pts;
  32734. gops.dts = gopForFusion.dts;
  32735. gops.duration += gopForFusion.duration;
  32736. } else {
  32737. // If we didn't find a candidate gop fall back to keyframe-pulling
  32738. gops = frameUtils.extendFirstKeyFrame(gops);
  32739. }
  32740. } // Trim gops to align with gopsToAlignWith
  32741. if (gopsToAlignWith.length) {
  32742. var alignedGops;
  32743. if (options.alignGopsAtEnd) {
  32744. alignedGops = this.alignGopsAtEnd_(gops);
  32745. } else {
  32746. alignedGops = this.alignGopsAtStart_(gops);
  32747. }
  32748. if (!alignedGops) {
  32749. // save all the nals in the last GOP into the gop cache
  32750. this.gopCache_.unshift({
  32751. gop: gops.pop(),
  32752. pps: track.pps,
  32753. sps: track.sps
  32754. }); // Keep a maximum of 6 GOPs in the cache
  32755. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  32756. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  32757. this.resetStream_();
  32758. this.trigger('done', 'VideoSegmentStream');
  32759. return;
  32760. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  32761. // when recalculated before sending off to CoalesceStream
  32762. trackDecodeInfo.clearDtsInfo(track);
  32763. gops = alignedGops;
  32764. }
  32765. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  32766. // samples (that is, frames) in the video data
  32767. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  32768. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  32769. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  32770. this.trigger('processedGopsInfo', gops.map(function (gop) {
  32771. return {
  32772. pts: gop.pts,
  32773. dts: gop.dts,
  32774. byteLength: gop.byteLength
  32775. };
  32776. }));
  32777. firstGop = gops[0];
  32778. lastGop = gops[gops.length - 1];
  32779. 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
  32780. this.gopCache_.unshift({
  32781. gop: gops.pop(),
  32782. pps: track.pps,
  32783. sps: track.sps
  32784. }); // Keep a maximum of 6 GOPs in the cache
  32785. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  32786. nalUnits = [];
  32787. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  32788. this.trigger('timelineStartInfo', track.timelineStartInfo);
  32789. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  32790. // throwing away hundreds of media segment fragments
  32791. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  32792. sequenceNumber++;
  32793. boxes.set(moof);
  32794. boxes.set(mdat, moof.byteLength);
  32795. this.trigger('data', {
  32796. track: track,
  32797. boxes: boxes
  32798. });
  32799. this.resetStream_(); // Continue with the flush process now
  32800. this.trigger('done', 'VideoSegmentStream');
  32801. };
  32802. this.resetStream_ = function () {
  32803. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  32804. // for instance, when we are rendition switching
  32805. config = undefined;
  32806. pps = undefined;
  32807. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  32808. // return it or return null if no good candidate was found
  32809. this.getGopForFusion_ = function (nalUnit) {
  32810. var halfSecond = 45000,
  32811. // Half-a-second in a 90khz clock
  32812. allowableOverlap = 10000,
  32813. // About 3 frames @ 30fps
  32814. nearestDistance = Infinity,
  32815. dtsDistance,
  32816. nearestGopObj,
  32817. currentGop,
  32818. currentGopObj,
  32819. i; // Search for the GOP nearest to the beginning of this nal unit
  32820. for (i = 0; i < this.gopCache_.length; i++) {
  32821. currentGopObj = this.gopCache_[i];
  32822. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  32823. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  32824. continue;
  32825. } // Reject Gops that would require a negative baseMediaDecodeTime
  32826. if (currentGop.dts < track.timelineStartInfo.dts) {
  32827. continue;
  32828. } // The distance between the end of the gop and the start of the nalUnit
  32829. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  32830. // a half-second of the nal unit
  32831. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  32832. // Always use the closest GOP we found if there is more than
  32833. // one candidate
  32834. if (!nearestGopObj || nearestDistance > dtsDistance) {
  32835. nearestGopObj = currentGopObj;
  32836. nearestDistance = dtsDistance;
  32837. }
  32838. }
  32839. }
  32840. if (nearestGopObj) {
  32841. return nearestGopObj.gop;
  32842. }
  32843. return null;
  32844. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  32845. // of gopsToAlignWith starting from the START of the list
  32846. this.alignGopsAtStart_ = function (gops) {
  32847. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  32848. byteLength = gops.byteLength;
  32849. nalCount = gops.nalCount;
  32850. duration = gops.duration;
  32851. alignIndex = gopIndex = 0;
  32852. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  32853. align = gopsToAlignWith[alignIndex];
  32854. gop = gops[gopIndex];
  32855. if (align.pts === gop.pts) {
  32856. break;
  32857. }
  32858. if (gop.pts > align.pts) {
  32859. // this current gop starts after the current gop we want to align on, so increment
  32860. // align index
  32861. alignIndex++;
  32862. continue;
  32863. } // current gop starts before the current gop we want to align on. so increment gop
  32864. // index
  32865. gopIndex++;
  32866. byteLength -= gop.byteLength;
  32867. nalCount -= gop.nalCount;
  32868. duration -= gop.duration;
  32869. }
  32870. if (gopIndex === 0) {
  32871. // no gops to trim
  32872. return gops;
  32873. }
  32874. if (gopIndex === gops.length) {
  32875. // all gops trimmed, skip appending all gops
  32876. return null;
  32877. }
  32878. alignedGops = gops.slice(gopIndex);
  32879. alignedGops.byteLength = byteLength;
  32880. alignedGops.duration = duration;
  32881. alignedGops.nalCount = nalCount;
  32882. alignedGops.pts = alignedGops[0].pts;
  32883. alignedGops.dts = alignedGops[0].dts;
  32884. return alignedGops;
  32885. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  32886. // of gopsToAlignWith starting from the END of the list
  32887. this.alignGopsAtEnd_ = function (gops) {
  32888. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  32889. alignIndex = gopsToAlignWith.length - 1;
  32890. gopIndex = gops.length - 1;
  32891. alignEndIndex = null;
  32892. matchFound = false;
  32893. while (alignIndex >= 0 && gopIndex >= 0) {
  32894. align = gopsToAlignWith[alignIndex];
  32895. gop = gops[gopIndex];
  32896. if (align.pts === gop.pts) {
  32897. matchFound = true;
  32898. break;
  32899. }
  32900. if (align.pts > gop.pts) {
  32901. alignIndex--;
  32902. continue;
  32903. }
  32904. if (alignIndex === gopsToAlignWith.length - 1) {
  32905. // gop.pts is greater than the last alignment candidate. If no match is found
  32906. // by the end of this loop, we still want to append gops that come after this
  32907. // point
  32908. alignEndIndex = gopIndex;
  32909. }
  32910. gopIndex--;
  32911. }
  32912. if (!matchFound && alignEndIndex === null) {
  32913. return null;
  32914. }
  32915. var trimIndex;
  32916. if (matchFound) {
  32917. trimIndex = gopIndex;
  32918. } else {
  32919. trimIndex = alignEndIndex;
  32920. }
  32921. if (trimIndex === 0) {
  32922. return gops;
  32923. }
  32924. var alignedGops = gops.slice(trimIndex);
  32925. var metadata = alignedGops.reduce(function (total, gop) {
  32926. total.byteLength += gop.byteLength;
  32927. total.duration += gop.duration;
  32928. total.nalCount += gop.nalCount;
  32929. return total;
  32930. }, {
  32931. byteLength: 0,
  32932. duration: 0,
  32933. nalCount: 0
  32934. });
  32935. alignedGops.byteLength = metadata.byteLength;
  32936. alignedGops.duration = metadata.duration;
  32937. alignedGops.nalCount = metadata.nalCount;
  32938. alignedGops.pts = alignedGops[0].pts;
  32939. alignedGops.dts = alignedGops[0].dts;
  32940. return alignedGops;
  32941. };
  32942. this.alignGopsWith = function (newGopsToAlignWith) {
  32943. gopsToAlignWith = newGopsToAlignWith;
  32944. };
  32945. };
  32946. _VideoSegmentStream.prototype = new stream();
  32947. /**
  32948. * A Stream that can combine multiple streams (ie. audio & video)
  32949. * into a single output segment for MSE. Also supports audio-only
  32950. * and video-only streams.
  32951. * @param options {object} transmuxer options object
  32952. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  32953. * in the source; false to adjust the first segment to start at media timeline start.
  32954. */
  32955. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  32956. // Number of Tracks per output segment
  32957. // If greater than 1, we combine multiple
  32958. // tracks into a single segment
  32959. this.numberOfTracks = 0;
  32960. this.metadataStream = metadataStream;
  32961. options = options || {};
  32962. if (typeof options.remux !== 'undefined') {
  32963. this.remuxTracks = !!options.remux;
  32964. } else {
  32965. this.remuxTracks = true;
  32966. }
  32967. if (typeof options.keepOriginalTimestamps === 'boolean') {
  32968. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  32969. }
  32970. this.pendingTracks = [];
  32971. this.videoTrack = null;
  32972. this.pendingBoxes = [];
  32973. this.pendingCaptions = [];
  32974. this.pendingMetadata = [];
  32975. this.pendingBytes = 0;
  32976. this.emittedTracks = 0;
  32977. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  32978. this.push = function (output) {
  32979. // buffer incoming captions until the associated video segment
  32980. // finishes
  32981. if (output.text) {
  32982. return this.pendingCaptions.push(output);
  32983. } // buffer incoming id3 tags until the final flush
  32984. if (output.frames) {
  32985. return this.pendingMetadata.push(output);
  32986. } // Add this track to the list of pending tracks and store
  32987. // important information required for the construction of
  32988. // the final segment
  32989. this.pendingTracks.push(output.track);
  32990. this.pendingBoxes.push(output.boxes);
  32991. this.pendingBytes += output.boxes.byteLength;
  32992. if (output.track.type === 'video') {
  32993. this.videoTrack = output.track;
  32994. }
  32995. if (output.track.type === 'audio') {
  32996. this.audioTrack = output.track;
  32997. }
  32998. };
  32999. };
  33000. _CoalesceStream.prototype = new stream();
  33001. _CoalesceStream.prototype.flush = function (flushSource) {
  33002. var offset = 0,
  33003. event = {
  33004. captions: [],
  33005. captionStreams: {},
  33006. metadata: [],
  33007. info: {}
  33008. },
  33009. caption,
  33010. id3,
  33011. initSegment,
  33012. timelineStartPts = 0,
  33013. i;
  33014. if (this.pendingTracks.length < this.numberOfTracks) {
  33015. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  33016. // Return because we haven't received a flush from a data-generating
  33017. // portion of the segment (meaning that we have only recieved meta-data
  33018. // or captions.)
  33019. return;
  33020. } else if (this.remuxTracks) {
  33021. // Return until we have enough tracks from the pipeline to remux (if we
  33022. // are remuxing audio and video into a single MP4)
  33023. return;
  33024. } else if (this.pendingTracks.length === 0) {
  33025. // In the case where we receive a flush without any data having been
  33026. // received we consider it an emitted track for the purposes of coalescing
  33027. // `done` events.
  33028. // We do this for the case where there is an audio and video track in the
  33029. // segment but no audio data. (seen in several playlists with alternate
  33030. // audio tracks and no audio present in the main TS segments.)
  33031. this.emittedTracks++;
  33032. if (this.emittedTracks >= this.numberOfTracks) {
  33033. this.trigger('done');
  33034. this.emittedTracks = 0;
  33035. }
  33036. return;
  33037. }
  33038. }
  33039. if (this.videoTrack) {
  33040. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  33041. VIDEO_PROPERTIES.forEach(function (prop) {
  33042. event.info[prop] = this.videoTrack[prop];
  33043. }, this);
  33044. } else if (this.audioTrack) {
  33045. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  33046. AUDIO_PROPERTIES.forEach(function (prop) {
  33047. event.info[prop] = this.audioTrack[prop];
  33048. }, this);
  33049. }
  33050. if (this.pendingTracks.length === 1) {
  33051. event.type = this.pendingTracks[0].type;
  33052. } else {
  33053. event.type = 'combined';
  33054. }
  33055. this.emittedTracks += this.pendingTracks.length;
  33056. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  33057. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  33058. // and track definitions
  33059. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  33060. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  33061. for (i = 0; i < this.pendingBoxes.length; i++) {
  33062. event.data.set(this.pendingBoxes[i], offset);
  33063. offset += this.pendingBoxes[i].byteLength;
  33064. } // Translate caption PTS times into second offsets to match the
  33065. // video timeline for the segment, and add track info
  33066. for (i = 0; i < this.pendingCaptions.length; i++) {
  33067. caption = this.pendingCaptions[i];
  33068. caption.startTime = caption.startPts;
  33069. if (!this.keepOriginalTimestamps) {
  33070. caption.startTime -= timelineStartPts;
  33071. }
  33072. caption.startTime /= 90e3;
  33073. caption.endTime = caption.endPts;
  33074. if (!this.keepOriginalTimestamps) {
  33075. caption.endTime -= timelineStartPts;
  33076. }
  33077. caption.endTime /= 90e3;
  33078. event.captionStreams[caption.stream] = true;
  33079. event.captions.push(caption);
  33080. } // Translate ID3 frame PTS times into second offsets to match the
  33081. // video timeline for the segment
  33082. for (i = 0; i < this.pendingMetadata.length; i++) {
  33083. id3 = this.pendingMetadata[i];
  33084. id3.cueTime = id3.pts;
  33085. if (!this.keepOriginalTimestamps) {
  33086. id3.cueTime -= timelineStartPts;
  33087. }
  33088. id3.cueTime /= 90e3;
  33089. event.metadata.push(id3);
  33090. } // We add this to every single emitted segment even though we only need
  33091. // it for the first
  33092. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  33093. this.pendingTracks.length = 0;
  33094. this.videoTrack = null;
  33095. this.pendingBoxes.length = 0;
  33096. this.pendingCaptions.length = 0;
  33097. this.pendingBytes = 0;
  33098. this.pendingMetadata.length = 0; // Emit the built segment
  33099. this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
  33100. if (this.emittedTracks >= this.numberOfTracks) {
  33101. this.trigger('done');
  33102. this.emittedTracks = 0;
  33103. }
  33104. };
  33105. /**
  33106. * A Stream that expects MP2T binary data as input and produces
  33107. * corresponding media segments, suitable for use with Media Source
  33108. * Extension (MSE) implementations that support the ISO BMFF byte
  33109. * stream format, like Chrome.
  33110. */
  33111. _Transmuxer = function Transmuxer(options) {
  33112. var self = this,
  33113. hasFlushed = true,
  33114. videoTrack,
  33115. audioTrack;
  33116. _Transmuxer.prototype.init.call(this);
  33117. options = options || {};
  33118. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  33119. this.transmuxPipeline_ = {};
  33120. this.setupAacPipeline = function () {
  33121. var pipeline = {};
  33122. this.transmuxPipeline_ = pipeline;
  33123. pipeline.type = 'aac';
  33124. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  33125. pipeline.aacStream = new aac();
  33126. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  33127. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  33128. pipeline.adtsStream = new adts();
  33129. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  33130. pipeline.headOfPipeline = pipeline.aacStream;
  33131. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  33132. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  33133. pipeline.metadataStream.on('timestamp', function (frame) {
  33134. pipeline.aacStream.setTimestamp(frame.timeStamp);
  33135. });
  33136. pipeline.aacStream.on('data', function (data) {
  33137. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  33138. audioTrack = audioTrack || {
  33139. timelineStartInfo: {
  33140. baseMediaDecodeTime: self.baseMediaDecodeTime
  33141. },
  33142. codec: 'adts',
  33143. type: 'audio'
  33144. }; // hook up the audio segment stream to the first track with aac data
  33145. pipeline.coalesceStream.numberOfTracks++;
  33146. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  33147. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  33148. }
  33149. }); // Re-emit any data coming from the coalesce stream to the outside world
  33150. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  33151. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  33152. };
  33153. this.setupTsPipeline = function () {
  33154. var pipeline = {};
  33155. this.transmuxPipeline_ = pipeline;
  33156. pipeline.type = 'ts';
  33157. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  33158. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  33159. pipeline.parseStream = new m2ts_1.TransportParseStream();
  33160. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  33161. pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  33162. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  33163. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  33164. pipeline.adtsStream = new adts();
  33165. pipeline.h264Stream = new H264Stream();
  33166. pipeline.captionStream = new m2ts_1.CaptionStream();
  33167. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  33168. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  33169. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  33170. // demux the streams
  33171. pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
  33172. pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  33173. pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  33174. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  33175. pipeline.elementaryStream.on('data', function (data) {
  33176. var i;
  33177. if (data.type === 'metadata') {
  33178. i = data.tracks.length; // scan the tracks listed in the metadata
  33179. while (i--) {
  33180. if (!videoTrack && data.tracks[i].type === 'video') {
  33181. videoTrack = data.tracks[i];
  33182. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  33183. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  33184. audioTrack = data.tracks[i];
  33185. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  33186. }
  33187. } // hook up the video segment stream to the first track with h264 data
  33188. if (videoTrack && !pipeline.videoSegmentStream) {
  33189. pipeline.coalesceStream.numberOfTracks++;
  33190. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  33191. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  33192. // When video emits timelineStartInfo data after a flush, we forward that
  33193. // info to the AudioSegmentStream, if it exists, because video timeline
  33194. // data takes precedence.
  33195. if (audioTrack) {
  33196. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  33197. // very earliest DTS we have seen in video because Chrome will
  33198. // interpret any video track with a baseMediaDecodeTime that is
  33199. // non-zero as a gap.
  33200. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  33201. }
  33202. });
  33203. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  33204. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  33205. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  33206. if (audioTrack) {
  33207. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  33208. }
  33209. }); // Set up the final part of the video pipeline
  33210. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  33211. }
  33212. if (audioTrack && !pipeline.audioSegmentStream) {
  33213. // hook up the audio segment stream to the first track with aac data
  33214. pipeline.coalesceStream.numberOfTracks++;
  33215. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  33216. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  33217. }
  33218. }
  33219. }); // Re-emit any data coming from the coalesce stream to the outside world
  33220. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  33221. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  33222. }; // hook up the segment streams once track metadata is delivered
  33223. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  33224. var pipeline = this.transmuxPipeline_;
  33225. if (!options.keepOriginalTimestamps) {
  33226. this.baseMediaDecodeTime = baseMediaDecodeTime;
  33227. }
  33228. if (audioTrack) {
  33229. audioTrack.timelineStartInfo.dts = undefined;
  33230. audioTrack.timelineStartInfo.pts = undefined;
  33231. trackDecodeInfo.clearDtsInfo(audioTrack);
  33232. if (!options.keepOriginalTimestamps) {
  33233. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  33234. }
  33235. if (pipeline.audioTimestampRolloverStream) {
  33236. pipeline.audioTimestampRolloverStream.discontinuity();
  33237. }
  33238. }
  33239. if (videoTrack) {
  33240. if (pipeline.videoSegmentStream) {
  33241. pipeline.videoSegmentStream.gopCache_ = [];
  33242. pipeline.videoTimestampRolloverStream.discontinuity();
  33243. }
  33244. videoTrack.timelineStartInfo.dts = undefined;
  33245. videoTrack.timelineStartInfo.pts = undefined;
  33246. trackDecodeInfo.clearDtsInfo(videoTrack);
  33247. pipeline.captionStream.reset();
  33248. if (!options.keepOriginalTimestamps) {
  33249. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  33250. }
  33251. }
  33252. if (pipeline.timedMetadataTimestampRolloverStream) {
  33253. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  33254. }
  33255. };
  33256. this.setAudioAppendStart = function (timestamp) {
  33257. if (audioTrack) {
  33258. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  33259. }
  33260. };
  33261. this.alignGopsWith = function (gopsToAlignWith) {
  33262. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  33263. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  33264. }
  33265. }; // feed incoming data to the front of the parsing pipeline
  33266. this.push = function (data) {
  33267. if (hasFlushed) {
  33268. var isAac = isLikelyAacData$1(data);
  33269. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  33270. this.setupAacPipeline();
  33271. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  33272. this.setupTsPipeline();
  33273. }
  33274. hasFlushed = false;
  33275. }
  33276. this.transmuxPipeline_.headOfPipeline.push(data);
  33277. }; // flush any buffered data
  33278. this.flush = function () {
  33279. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  33280. this.transmuxPipeline_.headOfPipeline.flush();
  33281. }; // Caption data has to be reset when seeking outside buffered range
  33282. this.resetCaptions = function () {
  33283. if (this.transmuxPipeline_.captionStream) {
  33284. this.transmuxPipeline_.captionStream.reset();
  33285. }
  33286. };
  33287. };
  33288. _Transmuxer.prototype = new stream();
  33289. var transmuxer = {
  33290. Transmuxer: _Transmuxer,
  33291. VideoSegmentStream: _VideoSegmentStream,
  33292. AudioSegmentStream: _AudioSegmentStream,
  33293. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  33294. VIDEO_PROPERTIES: VIDEO_PROPERTIES,
  33295. // exported for testing
  33296. generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
  33297. };
  33298. var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
  33299. var CaptionStream$1 = captionStream.CaptionStream;
  33300. /**
  33301. * Maps an offset in the mdat to a sample based on the the size of the samples.
  33302. * Assumes that `parseSamples` has been called first.
  33303. *
  33304. * @param {Number} offset - The offset into the mdat
  33305. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  33306. * @return {?Object} The matching sample, or null if no match was found.
  33307. *
  33308. * @see ISO-BMFF-12/2015, Section 8.8.8
  33309. **/
  33310. var mapToSample = function mapToSample(offset, samples) {
  33311. var approximateOffset = offset;
  33312. for (var i = 0; i < samples.length; i++) {
  33313. var sample = samples[i];
  33314. if (approximateOffset < sample.size) {
  33315. return sample;
  33316. }
  33317. approximateOffset -= sample.size;
  33318. }
  33319. return null;
  33320. };
  33321. /**
  33322. * Finds SEI nal units contained in a Media Data Box.
  33323. * Assumes that `parseSamples` has been called first.
  33324. *
  33325. * @param {Uint8Array} avcStream - The bytes of the mdat
  33326. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  33327. * @param {Number} trackId - The trackId of this video track
  33328. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  33329. * The contents of the seiNal should match what is expected by
  33330. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  33331. *
  33332. * @see ISO-BMFF-12/2015, Section 8.1.1
  33333. * @see Rec. ITU-T H.264, 7.3.2.3.1
  33334. **/
  33335. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  33336. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  33337. result = [],
  33338. seiNal,
  33339. i,
  33340. length,
  33341. lastMatchedSample;
  33342. for (i = 0; i + 4 < avcStream.length; i += length) {
  33343. length = avcView.getUint32(i);
  33344. i += 4; // Bail if this doesn't appear to be an H264 stream
  33345. if (length <= 0) {
  33346. continue;
  33347. }
  33348. switch (avcStream[i] & 0x1F) {
  33349. case 0x06:
  33350. var data = avcStream.subarray(i + 1, i + 1 + length);
  33351. var matchingSample = mapToSample(i, samples);
  33352. seiNal = {
  33353. nalUnitType: 'sei_rbsp',
  33354. size: length,
  33355. data: data,
  33356. escapedRBSP: discardEmulationPreventionBytes$1(data),
  33357. trackId: trackId
  33358. };
  33359. if (matchingSample) {
  33360. seiNal.pts = matchingSample.pts;
  33361. seiNal.dts = matchingSample.dts;
  33362. lastMatchedSample = matchingSample;
  33363. } else {
  33364. // If a matching sample cannot be found, use the last
  33365. // sample's values as they should be as close as possible
  33366. seiNal.pts = lastMatchedSample.pts;
  33367. seiNal.dts = lastMatchedSample.dts;
  33368. }
  33369. result.push(seiNal);
  33370. break;
  33371. default:
  33372. break;
  33373. }
  33374. }
  33375. return result;
  33376. };
  33377. /**
  33378. * Parses sample information out of Track Run Boxes and calculates
  33379. * the absolute presentation and decode timestamps of each sample.
  33380. *
  33381. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  33382. * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
  33383. @see ISO-BMFF-12/2015, Section 8.8.12
  33384. * @param {Object} tfhd - The parsed Track Fragment Header
  33385. * @see inspect.parseTfhd
  33386. * @return {Object[]} the parsed samples
  33387. *
  33388. * @see ISO-BMFF-12/2015, Section 8.8.8
  33389. **/
  33390. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  33391. var currentDts = baseMediaDecodeTime;
  33392. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  33393. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  33394. var trackId = tfhd.trackId;
  33395. var allSamples = [];
  33396. truns.forEach(function (trun) {
  33397. // Note: We currently do not parse the sample table as well
  33398. // as the trun. It's possible some sources will require this.
  33399. // moov > trak > mdia > minf > stbl
  33400. var trackRun = mp4Inspector.parseTrun(trun);
  33401. var samples = trackRun.samples;
  33402. samples.forEach(function (sample) {
  33403. if (sample.duration === undefined) {
  33404. sample.duration = defaultSampleDuration;
  33405. }
  33406. if (sample.size === undefined) {
  33407. sample.size = defaultSampleSize;
  33408. }
  33409. sample.trackId = trackId;
  33410. sample.dts = currentDts;
  33411. if (sample.compositionTimeOffset === undefined) {
  33412. sample.compositionTimeOffset = 0;
  33413. }
  33414. sample.pts = currentDts + sample.compositionTimeOffset;
  33415. currentDts += sample.duration;
  33416. });
  33417. allSamples = allSamples.concat(samples);
  33418. });
  33419. return allSamples;
  33420. };
  33421. /**
  33422. * Parses out caption nals from an FMP4 segment's video tracks.
  33423. *
  33424. * @param {Uint8Array} segment - The bytes of a single segment
  33425. * @param {Number} videoTrackId - The trackId of a video track in the segment
  33426. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  33427. * a list of seiNals found in that track
  33428. **/
  33429. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  33430. // To get the samples
  33431. var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
  33432. var mdats = probe.findBox(segment, ['mdat']);
  33433. var captionNals = {};
  33434. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  33435. mdats.forEach(function (mdat, index) {
  33436. var matchingTraf = trafs[index];
  33437. mdatTrafPairs.push({
  33438. mdat: mdat,
  33439. traf: matchingTraf
  33440. });
  33441. });
  33442. mdatTrafPairs.forEach(function (pair) {
  33443. var mdat = pair.mdat;
  33444. var traf = pair.traf;
  33445. var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
  33446. var headerInfo = mp4Inspector.parseTfhd(tfhd[0]);
  33447. var trackId = headerInfo.trackId;
  33448. var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  33449. var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  33450. var truns = probe.findBox(traf, ['trun']);
  33451. var samples;
  33452. var seiNals; // Only parse video data for the chosen video track
  33453. if (videoTrackId === trackId && truns.length > 0) {
  33454. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  33455. seiNals = findSeiNals(mdat, samples, trackId);
  33456. if (!captionNals[trackId]) {
  33457. captionNals[trackId] = [];
  33458. }
  33459. captionNals[trackId] = captionNals[trackId].concat(seiNals);
  33460. }
  33461. });
  33462. return captionNals;
  33463. };
  33464. /**
  33465. * Parses out inband captions from an MP4 container and returns
  33466. * caption objects that can be used by WebVTT and the TextTrack API.
  33467. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  33468. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  33469. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  33470. *
  33471. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  33472. * @param {Number} trackId - The id of the video track to parse
  33473. * @param {Number} timescale - The timescale for the video track from the init segment
  33474. *
  33475. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  33476. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  33477. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  33478. * @return {String} parsedCaptions[].text - The visible content of the caption
  33479. **/
  33480. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  33481. var seiNals;
  33482. if (!trackId) {
  33483. return null;
  33484. }
  33485. seiNals = parseCaptionNals(segment, trackId);
  33486. return {
  33487. seiNals: seiNals[trackId],
  33488. timescale: timescale
  33489. };
  33490. };
  33491. /**
  33492. * Converts SEI NALUs into captions that can be used by video.js
  33493. **/
  33494. var CaptionParser = function CaptionParser() {
  33495. var isInitialized = false;
  33496. var captionStream; // Stores segments seen before trackId and timescale are set
  33497. var segmentCache; // Stores video track ID of the track being parsed
  33498. var trackId; // Stores the timescale of the track being parsed
  33499. var timescale; // Stores captions parsed so far
  33500. var parsedCaptions;
  33501. /**
  33502. * A method to indicate whether a CaptionParser has been initalized
  33503. * @returns {Boolean}
  33504. **/
  33505. this.isInitialized = function () {
  33506. return isInitialized;
  33507. };
  33508. /**
  33509. * Initializes the underlying CaptionStream, SEI NAL parsing
  33510. * and management, and caption collection
  33511. **/
  33512. this.init = function () {
  33513. captionStream = new CaptionStream$1();
  33514. isInitialized = true; // Collect dispatched captions
  33515. captionStream.on('data', function (event) {
  33516. // Convert to seconds in the source's timescale
  33517. event.startTime = event.startPts / timescale;
  33518. event.endTime = event.endPts / timescale;
  33519. parsedCaptions.captions.push(event);
  33520. parsedCaptions.captionStreams[event.stream] = true;
  33521. });
  33522. };
  33523. /**
  33524. * Determines if a new video track will be selected
  33525. * or if the timescale changed
  33526. * @return {Boolean}
  33527. **/
  33528. this.isNewInit = function (videoTrackIds, timescales) {
  33529. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  33530. return false;
  33531. }
  33532. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  33533. };
  33534. /**
  33535. * Parses out SEI captions and interacts with underlying
  33536. * CaptionStream to return dispatched captions
  33537. *
  33538. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  33539. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  33540. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  33541. * @see parseEmbeddedCaptions
  33542. * @see m2ts/caption-stream.js
  33543. **/
  33544. this.parse = function (segment, videoTrackIds, timescales) {
  33545. var parsedData;
  33546. if (!this.isInitialized()) {
  33547. return null; // This is not likely to be a video segment
  33548. } else if (!videoTrackIds || !timescales) {
  33549. return null;
  33550. } else if (this.isNewInit(videoTrackIds, timescales)) {
  33551. // Use the first video track only as there is no
  33552. // mechanism to switch to other video tracks
  33553. trackId = videoTrackIds[0];
  33554. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  33555. // data until we have one
  33556. } else if (!trackId || !timescale) {
  33557. segmentCache.push(segment);
  33558. return null;
  33559. } // Now that a timescale and trackId is set, parse cached segments
  33560. while (segmentCache.length > 0) {
  33561. var cachedSegment = segmentCache.shift();
  33562. this.parse(cachedSegment, videoTrackIds, timescales);
  33563. }
  33564. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  33565. if (parsedData === null || !parsedData.seiNals) {
  33566. return null;
  33567. }
  33568. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  33569. this.flushStream();
  33570. return parsedCaptions;
  33571. };
  33572. /**
  33573. * Pushes SEI NALUs onto CaptionStream
  33574. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  33575. * Assumes that `parseCaptionNals` has been called first
  33576. * @see m2ts/caption-stream.js
  33577. **/
  33578. this.pushNals = function (nals) {
  33579. if (!this.isInitialized() || !nals || nals.length === 0) {
  33580. return null;
  33581. }
  33582. nals.forEach(function (nal) {
  33583. captionStream.push(nal);
  33584. });
  33585. };
  33586. /**
  33587. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  33588. * @see m2ts/caption-stream.js
  33589. **/
  33590. this.flushStream = function () {
  33591. if (!this.isInitialized()) {
  33592. return null;
  33593. }
  33594. captionStream.flush();
  33595. };
  33596. /**
  33597. * Reset caption buckets for new data
  33598. **/
  33599. this.clearParsedCaptions = function () {
  33600. parsedCaptions.captions = [];
  33601. parsedCaptions.captionStreams = {};
  33602. };
  33603. /**
  33604. * Resets underlying CaptionStream
  33605. * @see m2ts/caption-stream.js
  33606. **/
  33607. this.resetCaptionStream = function () {
  33608. if (!this.isInitialized()) {
  33609. return null;
  33610. }
  33611. captionStream.reset();
  33612. };
  33613. /**
  33614. * Convenience method to clear all captions flushed from the
  33615. * CaptionStream and still being parsed
  33616. * @see m2ts/caption-stream.js
  33617. **/
  33618. this.clearAllCaptions = function () {
  33619. this.clearParsedCaptions();
  33620. this.resetCaptionStream();
  33621. };
  33622. /**
  33623. * Reset caption parser
  33624. **/
  33625. this.reset = function () {
  33626. segmentCache = [];
  33627. trackId = null;
  33628. timescale = null;
  33629. if (!parsedCaptions) {
  33630. parsedCaptions = {
  33631. captions: [],
  33632. // CC1, CC2, CC3, CC4
  33633. captionStreams: {}
  33634. };
  33635. } else {
  33636. this.clearParsedCaptions();
  33637. }
  33638. this.resetCaptionStream();
  33639. };
  33640. this.reset();
  33641. };
  33642. var captionParser = CaptionParser;
  33643. /**
  33644. * mux.js
  33645. *
  33646. * Copyright (c) Brightcove
  33647. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  33648. */
  33649. var mp4 = {
  33650. generator: mp4Generator,
  33651. probe: probe,
  33652. Transmuxer: transmuxer.Transmuxer,
  33653. AudioSegmentStream: transmuxer.AudioSegmentStream,
  33654. VideoSegmentStream: transmuxer.VideoSegmentStream,
  33655. CaptionParser: captionParser
  33656. };
  33657. var mp4_6 = mp4.CaptionParser;
  33658. var parsePid = function parsePid(packet) {
  33659. var pid = packet[1] & 0x1f;
  33660. pid <<= 8;
  33661. pid |= packet[2];
  33662. return pid;
  33663. };
  33664. var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
  33665. return !!(packet[1] & 0x40);
  33666. };
  33667. var parseAdaptionField = function parseAdaptionField(packet) {
  33668. var offset = 0; // if an adaption field is present, its length is specified by the
  33669. // fifth byte of the TS packet header. The adaptation field is
  33670. // used to add stuffing to PES packets that don't fill a complete
  33671. // TS packet, and to specify some forms of timing and control data
  33672. // that we do not currently use.
  33673. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  33674. offset += packet[4] + 1;
  33675. }
  33676. return offset;
  33677. };
  33678. var parseType$3 = function parseType(packet, pmtPid) {
  33679. var pid = parsePid(packet);
  33680. if (pid === 0) {
  33681. return 'pat';
  33682. } else if (pid === pmtPid) {
  33683. return 'pmt';
  33684. } else if (pmtPid) {
  33685. return 'pes';
  33686. }
  33687. return null;
  33688. };
  33689. var parsePat = function parsePat(packet) {
  33690. var pusi = parsePayloadUnitStartIndicator(packet);
  33691. var offset = 4 + parseAdaptionField(packet);
  33692. if (pusi) {
  33693. offset += packet[offset] + 1;
  33694. }
  33695. return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
  33696. };
  33697. var parsePmt = function parsePmt(packet) {
  33698. var programMapTable = {};
  33699. var pusi = parsePayloadUnitStartIndicator(packet);
  33700. var payloadOffset = 4 + parseAdaptionField(packet);
  33701. if (pusi) {
  33702. payloadOffset += packet[payloadOffset] + 1;
  33703. } // PMTs can be sent ahead of the time when they should actually
  33704. // take effect. We don't believe this should ever be the case
  33705. // for HLS but we'll ignore "forward" PMT declarations if we see
  33706. // them. Future PMT declarations have the current_next_indicator
  33707. // set to zero.
  33708. if (!(packet[payloadOffset + 5] & 0x01)) {
  33709. return;
  33710. }
  33711. var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
  33712. sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
  33713. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  33714. // long the program info descriptors are
  33715. programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
  33716. var offset = 12 + programInfoLength;
  33717. while (offset < tableEnd) {
  33718. var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
  33719. programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
  33720. // skip past the elementary stream descriptors, if present
  33721. offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
  33722. }
  33723. return programMapTable;
  33724. };
  33725. var parsePesType = function parsePesType(packet, programMapTable) {
  33726. var pid = parsePid(packet);
  33727. var type = programMapTable[pid];
  33728. switch (type) {
  33729. case streamTypes.H264_STREAM_TYPE:
  33730. return 'video';
  33731. case streamTypes.ADTS_STREAM_TYPE:
  33732. return 'audio';
  33733. case streamTypes.METADATA_STREAM_TYPE:
  33734. return 'timed-metadata';
  33735. default:
  33736. return null;
  33737. }
  33738. };
  33739. var parsePesTime = function parsePesTime(packet) {
  33740. var pusi = parsePayloadUnitStartIndicator(packet);
  33741. if (!pusi) {
  33742. return null;
  33743. }
  33744. var offset = 4 + parseAdaptionField(packet);
  33745. if (offset >= packet.byteLength) {
  33746. // From the H 222.0 MPEG-TS spec
  33747. // "For transport stream packets carrying PES packets, stuffing is needed when there
  33748. // is insufficient PES packet data to completely fill the transport stream packet
  33749. // payload bytes. Stuffing is accomplished by defining an adaptation field longer than
  33750. // the sum of the lengths of the data elements in it, so that the payload bytes
  33751. // remaining after the adaptation field exactly accommodates the available PES packet
  33752. // data."
  33753. //
  33754. // If the offset is >= the length of the packet, then the packet contains no data
  33755. // and instead is just adaption field stuffing bytes
  33756. return null;
  33757. }
  33758. var pes = null;
  33759. var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
  33760. // and a DTS value. Determine what combination of values is
  33761. // available to work with.
  33762. ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  33763. // performs all bitwise operations on 32-bit integers but javascript
  33764. // supports a much greater range (52-bits) of integer using standard
  33765. // mathematical operations.
  33766. // We construct a 31-bit value using bitwise operators over the 31
  33767. // most significant bits and then multiply by 4 (equal to a left-shift
  33768. // of 2) before we add the final 2 least significant bits of the
  33769. // timestamp (equal to an OR.)
  33770. if (ptsDtsFlags & 0xC0) {
  33771. pes = {}; // the PTS and DTS are not written out directly. For information
  33772. // on how they are encoded, see
  33773. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  33774. 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;
  33775. pes.pts *= 4; // Left shift by 2
  33776. pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
  33777. pes.dts = pes.pts;
  33778. if (ptsDtsFlags & 0x40) {
  33779. 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;
  33780. pes.dts *= 4; // Left shift by 2
  33781. pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
  33782. }
  33783. }
  33784. return pes;
  33785. };
  33786. var parseNalUnitType = function parseNalUnitType(type) {
  33787. switch (type) {
  33788. case 0x05:
  33789. return 'slice_layer_without_partitioning_rbsp_idr';
  33790. case 0x06:
  33791. return 'sei_rbsp';
  33792. case 0x07:
  33793. return 'seq_parameter_set_rbsp';
  33794. case 0x08:
  33795. return 'pic_parameter_set_rbsp';
  33796. case 0x09:
  33797. return 'access_unit_delimiter_rbsp';
  33798. default:
  33799. return null;
  33800. }
  33801. };
  33802. var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
  33803. var offset = 4 + parseAdaptionField(packet);
  33804. var frameBuffer = packet.subarray(offset);
  33805. var frameI = 0;
  33806. var frameSyncPoint = 0;
  33807. var foundKeyFrame = false;
  33808. var nalType; // advance the sync point to a NAL start, if necessary
  33809. for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
  33810. if (frameBuffer[frameSyncPoint + 2] === 1) {
  33811. // the sync point is properly aligned
  33812. frameI = frameSyncPoint + 5;
  33813. break;
  33814. }
  33815. }
  33816. while (frameI < frameBuffer.byteLength) {
  33817. // look at the current byte to determine if we've hit the end of
  33818. // a NAL unit boundary
  33819. switch (frameBuffer[frameI]) {
  33820. case 0:
  33821. // skip past non-sync sequences
  33822. if (frameBuffer[frameI - 1] !== 0) {
  33823. frameI += 2;
  33824. break;
  33825. } else if (frameBuffer[frameI - 2] !== 0) {
  33826. frameI++;
  33827. break;
  33828. }
  33829. if (frameSyncPoint + 3 !== frameI - 2) {
  33830. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  33831. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  33832. foundKeyFrame = true;
  33833. }
  33834. } // drop trailing zeroes
  33835. do {
  33836. frameI++;
  33837. } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
  33838. frameSyncPoint = frameI - 2;
  33839. frameI += 3;
  33840. break;
  33841. case 1:
  33842. // skip past non-sync sequences
  33843. if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
  33844. frameI += 3;
  33845. break;
  33846. }
  33847. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  33848. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  33849. foundKeyFrame = true;
  33850. }
  33851. frameSyncPoint = frameI - 2;
  33852. frameI += 3;
  33853. break;
  33854. default:
  33855. // the current byte isn't a one or zero, so it cannot be part
  33856. // of a sync sequence
  33857. frameI += 3;
  33858. break;
  33859. }
  33860. }
  33861. frameBuffer = frameBuffer.subarray(frameSyncPoint);
  33862. frameI -= frameSyncPoint;
  33863. frameSyncPoint = 0; // parse the final nal
  33864. if (frameBuffer && frameBuffer.byteLength > 3) {
  33865. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  33866. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  33867. foundKeyFrame = true;
  33868. }
  33869. }
  33870. return foundKeyFrame;
  33871. };
  33872. var probe$1 = {
  33873. parseType: parseType$3,
  33874. parsePat: parsePat,
  33875. parsePmt: parsePmt,
  33876. parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
  33877. parsePesType: parsePesType,
  33878. parsePesTime: parsePesTime,
  33879. videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
  33880. };
  33881. var handleRollover$1 = timestampRolloverStream.handleRollover;
  33882. var probe$2 = {};
  33883. probe$2.ts = probe$1;
  33884. probe$2.aac = utils;
  33885. var PES_TIMESCALE = 90000,
  33886. MP2T_PACKET_LENGTH$1 = 188,
  33887. // bytes
  33888. SYNC_BYTE$1 = 0x47;
  33889. /**
  33890. * walks through segment data looking for pat and pmt packets to parse out
  33891. * program map table information
  33892. */
  33893. var parsePsi_ = function parsePsi_(bytes, pmt) {
  33894. var startIndex = 0,
  33895. endIndex = MP2T_PACKET_LENGTH$1,
  33896. packet,
  33897. type;
  33898. while (endIndex < bytes.byteLength) {
  33899. // Look for a pair of start and end sync bytes in the data..
  33900. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  33901. // We found a packet
  33902. packet = bytes.subarray(startIndex, endIndex);
  33903. type = probe$2.ts.parseType(packet, pmt.pid);
  33904. switch (type) {
  33905. case 'pat':
  33906. if (!pmt.pid) {
  33907. pmt.pid = probe$2.ts.parsePat(packet);
  33908. }
  33909. break;
  33910. case 'pmt':
  33911. if (!pmt.table) {
  33912. pmt.table = probe$2.ts.parsePmt(packet);
  33913. }
  33914. break;
  33915. default:
  33916. break;
  33917. } // Found the pat and pmt, we can stop walking the segment
  33918. if (pmt.pid && pmt.table) {
  33919. return;
  33920. }
  33921. startIndex += MP2T_PACKET_LENGTH$1;
  33922. endIndex += MP2T_PACKET_LENGTH$1;
  33923. continue;
  33924. } // If we get here, we have somehow become de-synchronized and we need to step
  33925. // forward one byte at a time until we find a pair of sync bytes that denote
  33926. // a packet
  33927. startIndex++;
  33928. endIndex++;
  33929. }
  33930. };
  33931. /**
  33932. * walks through the segment data from the start and end to get timing information
  33933. * for the first and last audio pes packets
  33934. */
  33935. var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
  33936. var startIndex = 0,
  33937. endIndex = MP2T_PACKET_LENGTH$1,
  33938. packet,
  33939. type,
  33940. pesType,
  33941. pusi,
  33942. parsed;
  33943. var endLoop = false; // Start walking from start of segment to get first audio packet
  33944. while (endIndex <= bytes.byteLength) {
  33945. // Look for a pair of start and end sync bytes in the data..
  33946. if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) {
  33947. // We found a packet
  33948. packet = bytes.subarray(startIndex, endIndex);
  33949. type = probe$2.ts.parseType(packet, pmt.pid);
  33950. switch (type) {
  33951. case 'pes':
  33952. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  33953. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  33954. if (pesType === 'audio' && pusi) {
  33955. parsed = probe$2.ts.parsePesTime(packet);
  33956. if (parsed) {
  33957. parsed.type = 'audio';
  33958. result.audio.push(parsed);
  33959. endLoop = true;
  33960. }
  33961. }
  33962. break;
  33963. default:
  33964. break;
  33965. }
  33966. if (endLoop) {
  33967. break;
  33968. }
  33969. startIndex += MP2T_PACKET_LENGTH$1;
  33970. endIndex += MP2T_PACKET_LENGTH$1;
  33971. continue;
  33972. } // If we get here, we have somehow become de-synchronized and we need to step
  33973. // forward one byte at a time until we find a pair of sync bytes that denote
  33974. // a packet
  33975. startIndex++;
  33976. endIndex++;
  33977. } // Start walking from end of segment to get last audio packet
  33978. endIndex = bytes.byteLength;
  33979. startIndex = endIndex - MP2T_PACKET_LENGTH$1;
  33980. endLoop = false;
  33981. while (startIndex >= 0) {
  33982. // Look for a pair of start and end sync bytes in the data..
  33983. if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) {
  33984. // We found a packet
  33985. packet = bytes.subarray(startIndex, endIndex);
  33986. type = probe$2.ts.parseType(packet, pmt.pid);
  33987. switch (type) {
  33988. case 'pes':
  33989. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  33990. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  33991. if (pesType === 'audio' && pusi) {
  33992. parsed = probe$2.ts.parsePesTime(packet);
  33993. if (parsed) {
  33994. parsed.type = 'audio';
  33995. result.audio.push(parsed);
  33996. endLoop = true;
  33997. }
  33998. }
  33999. break;
  34000. default:
  34001. break;
  34002. }
  34003. if (endLoop) {
  34004. break;
  34005. }
  34006. startIndex -= MP2T_PACKET_LENGTH$1;
  34007. endIndex -= MP2T_PACKET_LENGTH$1;
  34008. continue;
  34009. } // If we get here, we have somehow become de-synchronized and we need to step
  34010. // forward one byte at a time until we find a pair of sync bytes that denote
  34011. // a packet
  34012. startIndex--;
  34013. endIndex--;
  34014. }
  34015. };
  34016. /**
  34017. * walks through the segment data from the start and end to get timing information
  34018. * for the first and last video pes packets as well as timing information for the first
  34019. * key frame.
  34020. */
  34021. var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
  34022. var startIndex = 0,
  34023. endIndex = MP2T_PACKET_LENGTH$1,
  34024. packet,
  34025. type,
  34026. pesType,
  34027. pusi,
  34028. parsed,
  34029. frame,
  34030. i,
  34031. pes;
  34032. var endLoop = false;
  34033. var currentFrame = {
  34034. data: [],
  34035. size: 0
  34036. }; // Start walking from start of segment to get first video packet
  34037. while (endIndex < bytes.byteLength) {
  34038. // Look for a pair of start and end sync bytes in the data..
  34039. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  34040. // We found a packet
  34041. packet = bytes.subarray(startIndex, endIndex);
  34042. type = probe$2.ts.parseType(packet, pmt.pid);
  34043. switch (type) {
  34044. case 'pes':
  34045. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  34046. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  34047. if (pesType === 'video') {
  34048. if (pusi && !endLoop) {
  34049. parsed = probe$2.ts.parsePesTime(packet);
  34050. if (parsed) {
  34051. parsed.type = 'video';
  34052. result.video.push(parsed);
  34053. endLoop = true;
  34054. }
  34055. }
  34056. if (!result.firstKeyFrame) {
  34057. if (pusi) {
  34058. if (currentFrame.size !== 0) {
  34059. frame = new Uint8Array(currentFrame.size);
  34060. i = 0;
  34061. while (currentFrame.data.length) {
  34062. pes = currentFrame.data.shift();
  34063. frame.set(pes, i);
  34064. i += pes.byteLength;
  34065. }
  34066. if (probe$2.ts.videoPacketContainsKeyFrame(frame)) {
  34067. var firstKeyFrame = probe$2.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
  34068. // the keyframe seems to work fine with HLS playback
  34069. // and definitely preferable to a crash with TypeError...
  34070. if (firstKeyFrame) {
  34071. result.firstKeyFrame = firstKeyFrame;
  34072. result.firstKeyFrame.type = 'video';
  34073. } else {
  34074. // eslint-disable-next-line
  34075. console.warn('Failed to extract PTS/DTS from PES at first keyframe. ' + 'This could be an unusual TS segment, or else mux.js did not ' + 'parse your TS segment correctly. If you know your TS ' + 'segments do contain PTS/DTS on keyframes please file a bug ' + 'report! You can try ffprobe to double check for yourself.');
  34076. }
  34077. }
  34078. currentFrame.size = 0;
  34079. }
  34080. }
  34081. currentFrame.data.push(packet);
  34082. currentFrame.size += packet.byteLength;
  34083. }
  34084. }
  34085. break;
  34086. default:
  34087. break;
  34088. }
  34089. if (endLoop && result.firstKeyFrame) {
  34090. break;
  34091. }
  34092. startIndex += MP2T_PACKET_LENGTH$1;
  34093. endIndex += MP2T_PACKET_LENGTH$1;
  34094. continue;
  34095. } // If we get here, we have somehow become de-synchronized and we need to step
  34096. // forward one byte at a time until we find a pair of sync bytes that denote
  34097. // a packet
  34098. startIndex++;
  34099. endIndex++;
  34100. } // Start walking from end of segment to get last video packet
  34101. endIndex = bytes.byteLength;
  34102. startIndex = endIndex - MP2T_PACKET_LENGTH$1;
  34103. endLoop = false;
  34104. while (startIndex >= 0) {
  34105. // Look for a pair of start and end sync bytes in the data..
  34106. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  34107. // We found a packet
  34108. packet = bytes.subarray(startIndex, endIndex);
  34109. type = probe$2.ts.parseType(packet, pmt.pid);
  34110. switch (type) {
  34111. case 'pes':
  34112. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  34113. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  34114. if (pesType === 'video' && pusi) {
  34115. parsed = probe$2.ts.parsePesTime(packet);
  34116. if (parsed) {
  34117. parsed.type = 'video';
  34118. result.video.push(parsed);
  34119. endLoop = true;
  34120. }
  34121. }
  34122. break;
  34123. default:
  34124. break;
  34125. }
  34126. if (endLoop) {
  34127. break;
  34128. }
  34129. startIndex -= MP2T_PACKET_LENGTH$1;
  34130. endIndex -= MP2T_PACKET_LENGTH$1;
  34131. continue;
  34132. } // If we get here, we have somehow become de-synchronized and we need to step
  34133. // forward one byte at a time until we find a pair of sync bytes that denote
  34134. // a packet
  34135. startIndex--;
  34136. endIndex--;
  34137. }
  34138. };
  34139. /**
  34140. * Adjusts the timestamp information for the segment to account for
  34141. * rollover and convert to seconds based on pes packet timescale (90khz clock)
  34142. */
  34143. var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
  34144. if (segmentInfo.audio && segmentInfo.audio.length) {
  34145. var audioBaseTimestamp = baseTimestamp;
  34146. if (typeof audioBaseTimestamp === 'undefined') {
  34147. audioBaseTimestamp = segmentInfo.audio[0].dts;
  34148. }
  34149. segmentInfo.audio.forEach(function (info) {
  34150. info.dts = handleRollover$1(info.dts, audioBaseTimestamp);
  34151. info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds
  34152. info.dtsTime = info.dts / PES_TIMESCALE;
  34153. info.ptsTime = info.pts / PES_TIMESCALE;
  34154. });
  34155. }
  34156. if (segmentInfo.video && segmentInfo.video.length) {
  34157. var videoBaseTimestamp = baseTimestamp;
  34158. if (typeof videoBaseTimestamp === 'undefined') {
  34159. videoBaseTimestamp = segmentInfo.video[0].dts;
  34160. }
  34161. segmentInfo.video.forEach(function (info) {
  34162. info.dts = handleRollover$1(info.dts, videoBaseTimestamp);
  34163. info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds
  34164. info.dtsTime = info.dts / PES_TIMESCALE;
  34165. info.ptsTime = info.pts / PES_TIMESCALE;
  34166. });
  34167. if (segmentInfo.firstKeyFrame) {
  34168. var frame = segmentInfo.firstKeyFrame;
  34169. frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp);
  34170. frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds
  34171. frame.dtsTime = frame.dts / PES_TIMESCALE;
  34172. frame.ptsTime = frame.dts / PES_TIMESCALE;
  34173. }
  34174. }
  34175. };
  34176. /**
  34177. * inspects the aac data stream for start and end time information
  34178. */
  34179. var inspectAac_ = function inspectAac_(bytes) {
  34180. var endLoop = false,
  34181. audioCount = 0,
  34182. sampleRate = null,
  34183. timestamp = null,
  34184. frameSize = 0,
  34185. byteIndex = 0,
  34186. packet;
  34187. while (bytes.length - byteIndex >= 3) {
  34188. var type = probe$2.aac.parseType(bytes, byteIndex);
  34189. switch (type) {
  34190. case 'timed-metadata':
  34191. // Exit early because we don't have enough to parse
  34192. // the ID3 tag header
  34193. if (bytes.length - byteIndex < 10) {
  34194. endLoop = true;
  34195. break;
  34196. }
  34197. frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  34198. // to emit a full packet
  34199. if (frameSize > bytes.length) {
  34200. endLoop = true;
  34201. break;
  34202. }
  34203. if (timestamp === null) {
  34204. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  34205. timestamp = probe$2.aac.parseAacTimestamp(packet);
  34206. }
  34207. byteIndex += frameSize;
  34208. break;
  34209. case 'audio':
  34210. // Exit early because we don't have enough to parse
  34211. // the ADTS frame header
  34212. if (bytes.length - byteIndex < 7) {
  34213. endLoop = true;
  34214. break;
  34215. }
  34216. frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  34217. // to emit a full packet
  34218. if (frameSize > bytes.length) {
  34219. endLoop = true;
  34220. break;
  34221. }
  34222. if (sampleRate === null) {
  34223. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  34224. sampleRate = probe$2.aac.parseSampleRate(packet);
  34225. }
  34226. audioCount++;
  34227. byteIndex += frameSize;
  34228. break;
  34229. default:
  34230. byteIndex++;
  34231. break;
  34232. }
  34233. if (endLoop) {
  34234. return null;
  34235. }
  34236. }
  34237. if (sampleRate === null || timestamp === null) {
  34238. return null;
  34239. }
  34240. var audioTimescale = PES_TIMESCALE / sampleRate;
  34241. var result = {
  34242. audio: [{
  34243. type: 'audio',
  34244. dts: timestamp,
  34245. pts: timestamp
  34246. }, {
  34247. type: 'audio',
  34248. dts: timestamp + audioCount * 1024 * audioTimescale,
  34249. pts: timestamp + audioCount * 1024 * audioTimescale
  34250. }]
  34251. };
  34252. return result;
  34253. };
  34254. /**
  34255. * inspects the transport stream segment data for start and end time information
  34256. * of the audio and video tracks (when present) as well as the first key frame's
  34257. * start time.
  34258. */
  34259. var inspectTs_ = function inspectTs_(bytes) {
  34260. var pmt = {
  34261. pid: null,
  34262. table: null
  34263. };
  34264. var result = {};
  34265. parsePsi_(bytes, pmt);
  34266. for (var pid in pmt.table) {
  34267. if (pmt.table.hasOwnProperty(pid)) {
  34268. var type = pmt.table[pid];
  34269. switch (type) {
  34270. case streamTypes.H264_STREAM_TYPE:
  34271. result.video = [];
  34272. parseVideoPes_(bytes, pmt, result);
  34273. if (result.video.length === 0) {
  34274. delete result.video;
  34275. }
  34276. break;
  34277. case streamTypes.ADTS_STREAM_TYPE:
  34278. result.audio = [];
  34279. parseAudioPes_(bytes, pmt, result);
  34280. if (result.audio.length === 0) {
  34281. delete result.audio;
  34282. }
  34283. break;
  34284. default:
  34285. break;
  34286. }
  34287. }
  34288. }
  34289. return result;
  34290. };
  34291. /**
  34292. * Inspects segment byte data and returns an object with start and end timing information
  34293. *
  34294. * @param {Uint8Array} bytes The segment byte data
  34295. * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
  34296. * timestamps for rollover. This value must be in 90khz clock.
  34297. * @return {Object} Object containing start and end frame timing info of segment.
  34298. */
  34299. var inspect = function inspect(bytes, baseTimestamp) {
  34300. var isAacData = probe$2.aac.isLikelyAacData(bytes);
  34301. var result;
  34302. if (isAacData) {
  34303. result = inspectAac_(bytes);
  34304. } else {
  34305. result = inspectTs_(bytes);
  34306. }
  34307. if (!result || !result.audio && !result.video) {
  34308. return null;
  34309. }
  34310. adjustTimestamp_(result, baseTimestamp);
  34311. return result;
  34312. };
  34313. var tsInspector = {
  34314. inspect: inspect,
  34315. parseAudioPes_: parseAudioPes_
  34316. };
  34317. /*
  34318. * pkcs7.pad
  34319. * https://github.com/brightcove/pkcs7
  34320. *
  34321. * Copyright (c) 2014 Brightcove
  34322. * Licensed under the apache2 license.
  34323. */
  34324. /**
  34325. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  34326. * @param padded {Uint8Array} unencrypted bytes that have been padded
  34327. * @return {Uint8Array} the unpadded bytes
  34328. * @see http://tools.ietf.org/html/rfc5652
  34329. */
  34330. function unpad(padded) {
  34331. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  34332. }
  34333. var classCallCheck = function classCallCheck(instance, Constructor) {
  34334. if (!(instance instanceof Constructor)) {
  34335. throw new TypeError("Cannot call a class as a function");
  34336. }
  34337. };
  34338. var createClass = function () {
  34339. function defineProperties(target, props) {
  34340. for (var i = 0; i < props.length; i++) {
  34341. var descriptor = props[i];
  34342. descriptor.enumerable = descriptor.enumerable || false;
  34343. descriptor.configurable = true;
  34344. if ("value" in descriptor) descriptor.writable = true;
  34345. Object.defineProperty(target, descriptor.key, descriptor);
  34346. }
  34347. }
  34348. return function (Constructor, protoProps, staticProps) {
  34349. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  34350. if (staticProps) defineProperties(Constructor, staticProps);
  34351. return Constructor;
  34352. };
  34353. }();
  34354. var inherits = function inherits(subClass, superClass) {
  34355. if (typeof superClass !== "function" && superClass !== null) {
  34356. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  34357. }
  34358. subClass.prototype = Object.create(superClass && superClass.prototype, {
  34359. constructor: {
  34360. value: subClass,
  34361. enumerable: false,
  34362. writable: true,
  34363. configurable: true
  34364. }
  34365. });
  34366. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  34367. };
  34368. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  34369. if (!self) {
  34370. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  34371. }
  34372. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  34373. };
  34374. /**
  34375. * @file aes.js
  34376. *
  34377. * This file contains an adaptation of the AES decryption algorithm
  34378. * from the Standford Javascript Cryptography Library. That work is
  34379. * covered by the following copyright and permissions notice:
  34380. *
  34381. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  34382. * All rights reserved.
  34383. *
  34384. * Redistribution and use in source and binary forms, with or without
  34385. * modification, are permitted provided that the following conditions are
  34386. * met:
  34387. *
  34388. * 1. Redistributions of source code must retain the above copyright
  34389. * notice, this list of conditions and the following disclaimer.
  34390. *
  34391. * 2. Redistributions in binary form must reproduce the above
  34392. * copyright notice, this list of conditions and the following
  34393. * disclaimer in the documentation and/or other materials provided
  34394. * with the distribution.
  34395. *
  34396. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  34397. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  34398. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  34399. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  34400. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  34401. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  34402. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  34403. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  34404. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  34405. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  34406. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34407. *
  34408. * The views and conclusions contained in the software and documentation
  34409. * are those of the authors and should not be interpreted as representing
  34410. * official policies, either expressed or implied, of the authors.
  34411. */
  34412. /**
  34413. * Expand the S-box tables.
  34414. *
  34415. * @private
  34416. */
  34417. var precompute = function precompute() {
  34418. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  34419. var encTable = tables[0];
  34420. var decTable = tables[1];
  34421. var sbox = encTable[4];
  34422. var sboxInv = decTable[4];
  34423. var i = void 0;
  34424. var x = void 0;
  34425. var xInv = void 0;
  34426. var d = [];
  34427. var th = [];
  34428. var x2 = void 0;
  34429. var x4 = void 0;
  34430. var x8 = void 0;
  34431. var s = void 0;
  34432. var tEnc = void 0;
  34433. var tDec = void 0; // Compute double and third tables
  34434. for (i = 0; i < 256; i++) {
  34435. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  34436. }
  34437. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  34438. // Compute sbox
  34439. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  34440. s = s >> 8 ^ s & 255 ^ 99;
  34441. sbox[x] = s;
  34442. sboxInv[s] = x; // Compute MixColumns
  34443. x8 = d[x4 = d[x2 = d[x]]];
  34444. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  34445. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  34446. for (i = 0; i < 4; i++) {
  34447. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  34448. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  34449. }
  34450. } // Compactify. Considerable speedup on Firefox.
  34451. for (i = 0; i < 5; i++) {
  34452. encTable[i] = encTable[i].slice(0);
  34453. decTable[i] = decTable[i].slice(0);
  34454. }
  34455. return tables;
  34456. };
  34457. var aesTables = null;
  34458. /**
  34459. * Schedule out an AES key for both encryption and decryption. This
  34460. * is a low-level class. Use a cipher mode to do bulk encryption.
  34461. *
  34462. * @class AES
  34463. * @param key {Array} The key as an array of 4, 6 or 8 words.
  34464. */
  34465. var AES = function () {
  34466. function AES(key) {
  34467. classCallCheck(this, AES);
  34468. /**
  34469. * The expanded S-box and inverse S-box tables. These will be computed
  34470. * on the client so that we don't have to send them down the wire.
  34471. *
  34472. * There are two tables, _tables[0] is for encryption and
  34473. * _tables[1] is for decryption.
  34474. *
  34475. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  34476. * last (_tables[01][4]) is the S-box itself.
  34477. *
  34478. * @private
  34479. */
  34480. // if we have yet to precompute the S-box tables
  34481. // do so now
  34482. if (!aesTables) {
  34483. aesTables = precompute();
  34484. } // then make a copy of that object for use
  34485. 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()]];
  34486. var i = void 0;
  34487. var j = void 0;
  34488. var tmp = void 0;
  34489. var encKey = void 0;
  34490. var decKey = void 0;
  34491. var sbox = this._tables[0][4];
  34492. var decTable = this._tables[1];
  34493. var keyLen = key.length;
  34494. var rcon = 1;
  34495. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  34496. throw new Error('Invalid aes key size');
  34497. }
  34498. encKey = key.slice(0);
  34499. decKey = [];
  34500. this._key = [encKey, decKey]; // schedule encryption keys
  34501. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  34502. tmp = encKey[i - 1]; // apply sbox
  34503. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  34504. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  34505. if (i % keyLen === 0) {
  34506. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  34507. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  34508. }
  34509. }
  34510. encKey[i] = encKey[i - keyLen] ^ tmp;
  34511. } // schedule decryption keys
  34512. for (j = 0; i; j++, i--) {
  34513. tmp = encKey[j & 3 ? i : i - 4];
  34514. if (i <= 4 || j < 4) {
  34515. decKey[j] = tmp;
  34516. } else {
  34517. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  34518. }
  34519. }
  34520. }
  34521. /**
  34522. * Decrypt 16 bytes, specified as four 32-bit words.
  34523. *
  34524. * @param {Number} encrypted0 the first word to decrypt
  34525. * @param {Number} encrypted1 the second word to decrypt
  34526. * @param {Number} encrypted2 the third word to decrypt
  34527. * @param {Number} encrypted3 the fourth word to decrypt
  34528. * @param {Int32Array} out the array to write the decrypted words
  34529. * into
  34530. * @param {Number} offset the offset into the output array to start
  34531. * writing results
  34532. * @return {Array} The plaintext.
  34533. */
  34534. AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  34535. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  34536. var a = encrypted0 ^ key[0];
  34537. var b = encrypted3 ^ key[1];
  34538. var c = encrypted2 ^ key[2];
  34539. var d = encrypted1 ^ key[3];
  34540. var a2 = void 0;
  34541. var b2 = void 0;
  34542. var c2 = void 0; // key.length === 2 ?
  34543. var nInnerRounds = key.length / 4 - 2;
  34544. var i = void 0;
  34545. var kIndex = 4;
  34546. var table = this._tables[1]; // load up the tables
  34547. var table0 = table[0];
  34548. var table1 = table[1];
  34549. var table2 = table[2];
  34550. var table3 = table[3];
  34551. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  34552. for (i = 0; i < nInnerRounds; i++) {
  34553. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  34554. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  34555. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  34556. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  34557. kIndex += 4;
  34558. a = a2;
  34559. b = b2;
  34560. c = c2;
  34561. } // Last round.
  34562. for (i = 0; i < 4; i++) {
  34563. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  34564. a2 = a;
  34565. a = b;
  34566. b = c;
  34567. c = d;
  34568. d = a2;
  34569. }
  34570. };
  34571. return AES;
  34572. }();
  34573. /**
  34574. * @file stream.js
  34575. */
  34576. /**
  34577. * A lightweight readable stream implemention that handles event dispatching.
  34578. *
  34579. * @class Stream
  34580. */
  34581. var Stream$2 = function () {
  34582. function Stream() {
  34583. classCallCheck(this, Stream);
  34584. this.listeners = {};
  34585. }
  34586. /**
  34587. * Add a listener for a specified event type.
  34588. *
  34589. * @param {String} type the event name
  34590. * @param {Function} listener the callback to be invoked when an event of
  34591. * the specified type occurs
  34592. */
  34593. Stream.prototype.on = function on(type, listener) {
  34594. if (!this.listeners[type]) {
  34595. this.listeners[type] = [];
  34596. }
  34597. this.listeners[type].push(listener);
  34598. };
  34599. /**
  34600. * Remove a listener for a specified event type.
  34601. *
  34602. * @param {String} type the event name
  34603. * @param {Function} listener a function previously registered for this
  34604. * type of event through `on`
  34605. * @return {Boolean} if we could turn it off or not
  34606. */
  34607. Stream.prototype.off = function off(type, listener) {
  34608. if (!this.listeners[type]) {
  34609. return false;
  34610. }
  34611. var index = this.listeners[type].indexOf(listener);
  34612. this.listeners[type].splice(index, 1);
  34613. return index > -1;
  34614. };
  34615. /**
  34616. * Trigger an event of the specified type on this stream. Any additional
  34617. * arguments to this function are passed as parameters to event listeners.
  34618. *
  34619. * @param {String} type the event name
  34620. */
  34621. Stream.prototype.trigger = function trigger(type) {
  34622. var callbacks = this.listeners[type];
  34623. if (!callbacks) {
  34624. return;
  34625. } // Slicing the arguments on every invocation of this method
  34626. // can add a significant amount of overhead. Avoid the
  34627. // intermediate object creation for the common case of a
  34628. // single callback argument
  34629. if (arguments.length === 2) {
  34630. var length = callbacks.length;
  34631. for (var i = 0; i < length; ++i) {
  34632. callbacks[i].call(this, arguments[1]);
  34633. }
  34634. } else {
  34635. var args = Array.prototype.slice.call(arguments, 1);
  34636. var _length = callbacks.length;
  34637. for (var _i = 0; _i < _length; ++_i) {
  34638. callbacks[_i].apply(this, args);
  34639. }
  34640. }
  34641. };
  34642. /**
  34643. * Destroys the stream and cleans up.
  34644. */
  34645. Stream.prototype.dispose = function dispose() {
  34646. this.listeners = {};
  34647. };
  34648. /**
  34649. * Forwards all `data` events on this stream to the destination stream. The
  34650. * destination stream should provide a method `push` to receive the data
  34651. * events as they arrive.
  34652. *
  34653. * @param {Stream} destination the stream that will receive all `data` events
  34654. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  34655. */
  34656. Stream.prototype.pipe = function pipe(destination) {
  34657. this.on('data', function (data) {
  34658. destination.push(data);
  34659. });
  34660. };
  34661. return Stream;
  34662. }();
  34663. /**
  34664. * @file async-stream.js
  34665. */
  34666. /**
  34667. * A wrapper around the Stream class to use setTiemout
  34668. * and run stream "jobs" Asynchronously
  34669. *
  34670. * @class AsyncStream
  34671. * @extends Stream
  34672. */
  34673. var AsyncStream = function (_Stream) {
  34674. inherits(AsyncStream, _Stream);
  34675. function AsyncStream() {
  34676. classCallCheck(this, AsyncStream);
  34677. var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2));
  34678. _this.jobs = [];
  34679. _this.delay = 1;
  34680. _this.timeout_ = null;
  34681. return _this;
  34682. }
  34683. /**
  34684. * process an async job
  34685. *
  34686. * @private
  34687. */
  34688. AsyncStream.prototype.processJob_ = function processJob_() {
  34689. this.jobs.shift()();
  34690. if (this.jobs.length) {
  34691. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  34692. } else {
  34693. this.timeout_ = null;
  34694. }
  34695. };
  34696. /**
  34697. * push a job into the stream
  34698. *
  34699. * @param {Function} job the job to push into the stream
  34700. */
  34701. AsyncStream.prototype.push = function push(job) {
  34702. this.jobs.push(job);
  34703. if (!this.timeout_) {
  34704. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  34705. }
  34706. };
  34707. return AsyncStream;
  34708. }(Stream$2);
  34709. /**
  34710. * @file decrypter.js
  34711. *
  34712. * An asynchronous implementation of AES-128 CBC decryption with
  34713. * PKCS#7 padding.
  34714. */
  34715. /**
  34716. * Convert network-order (big-endian) bytes into their little-endian
  34717. * representation.
  34718. */
  34719. var ntoh = function ntoh(word) {
  34720. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  34721. };
  34722. /**
  34723. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  34724. *
  34725. * @param {Uint8Array} encrypted the encrypted bytes
  34726. * @param {Uint32Array} key the bytes of the decryption key
  34727. * @param {Uint32Array} initVector the initialization vector (IV) to
  34728. * use for the first round of CBC.
  34729. * @return {Uint8Array} the decrypted bytes
  34730. *
  34731. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  34732. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  34733. * @see https://tools.ietf.org/html/rfc2315
  34734. */
  34735. var decrypt = function decrypt(encrypted, key, initVector) {
  34736. // word-level access to the encrypted bytes
  34737. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  34738. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  34739. var decrypted = new Uint8Array(encrypted.byteLength);
  34740. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  34741. // decrypted data
  34742. var init0 = void 0;
  34743. var init1 = void 0;
  34744. var init2 = void 0;
  34745. var init3 = void 0;
  34746. var encrypted0 = void 0;
  34747. var encrypted1 = void 0;
  34748. var encrypted2 = void 0;
  34749. var encrypted3 = void 0; // iteration variable
  34750. var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
  34751. // passed-in reference and easier access
  34752. init0 = initVector[0];
  34753. init1 = initVector[1];
  34754. init2 = initVector[2];
  34755. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  34756. // to each decrypted block
  34757. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  34758. // convert big-endian (network order) words into little-endian
  34759. // (javascript order)
  34760. encrypted0 = ntoh(encrypted32[wordIx]);
  34761. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  34762. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  34763. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  34764. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  34765. // plaintext
  34766. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  34767. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  34768. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  34769. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  34770. init0 = encrypted0;
  34771. init1 = encrypted1;
  34772. init2 = encrypted2;
  34773. init3 = encrypted3;
  34774. }
  34775. return decrypted;
  34776. };
  34777. /**
  34778. * The `Decrypter` class that manages decryption of AES
  34779. * data through `AsyncStream` objects and the `decrypt`
  34780. * function
  34781. *
  34782. * @param {Uint8Array} encrypted the encrypted bytes
  34783. * @param {Uint32Array} key the bytes of the decryption key
  34784. * @param {Uint32Array} initVector the initialization vector (IV) to
  34785. * @param {Function} done the function to run when done
  34786. * @class Decrypter
  34787. */
  34788. var Decrypter = function () {
  34789. function Decrypter(encrypted, key, initVector, done) {
  34790. classCallCheck(this, Decrypter);
  34791. var step = Decrypter.STEP;
  34792. var encrypted32 = new Int32Array(encrypted.buffer);
  34793. var decrypted = new Uint8Array(encrypted.byteLength);
  34794. var i = 0;
  34795. this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
  34796. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  34797. for (i = step; i < encrypted32.length; i += step) {
  34798. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  34799. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  34800. } // invoke the done() callback when everything is finished
  34801. this.asyncStream_.push(function () {
  34802. // remove pkcs#7 padding from the decrypted bytes
  34803. done(null, unpad(decrypted));
  34804. });
  34805. }
  34806. /**
  34807. * a getter for step the maximum number of bytes to process at one time
  34808. *
  34809. * @return {Number} the value of step 32000
  34810. */
  34811. /**
  34812. * @private
  34813. */
  34814. Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  34815. return function () {
  34816. var bytes = decrypt(encrypted, key, initVector);
  34817. decrypted.set(bytes, encrypted.byteOffset);
  34818. };
  34819. };
  34820. createClass(Decrypter, null, [{
  34821. key: 'STEP',
  34822. get: function get$$1() {
  34823. // 4 * 8000;
  34824. return 32000;
  34825. }
  34826. }]);
  34827. return Decrypter;
  34828. }();
  34829. /**
  34830. * @videojs/http-streaming
  34831. * @version 1.10.3
  34832. * @copyright 2019 Brightcove, Inc
  34833. * @license Apache-2.0
  34834. */
  34835. /**
  34836. * @file resolve-url.js - Handling how URLs are resolved and manipulated
  34837. */
  34838. var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) {
  34839. // return early if we don't need to resolve
  34840. if (/^[a-z]+:/i.test(relativeURL)) {
  34841. return relativeURL;
  34842. } // if the base URL is relative then combine with the current location
  34843. if (!/\/\//i.test(baseURL)) {
  34844. baseURL = urlToolkit.buildAbsoluteURL(window$1.location.href, baseURL);
  34845. }
  34846. return urlToolkit.buildAbsoluteURL(baseURL, relativeURL);
  34847. };
  34848. /**
  34849. * Checks whether xhr request was redirected and returns correct url depending
  34850. * on `handleManifestRedirects` option
  34851. *
  34852. * @api private
  34853. *
  34854. * @param {String} url - an url being requested
  34855. * @param {XMLHttpRequest} req - xhr request result
  34856. *
  34857. * @return {String}
  34858. */
  34859. var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
  34860. // To understand how the responseURL below is set and generated:
  34861. // - https://fetch.spec.whatwg.org/#concept-response-url
  34862. // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
  34863. if (handleManifestRedirect && req.responseURL && url !== req.responseURL) {
  34864. return req.responseURL;
  34865. }
  34866. return url;
  34867. };
  34868. var classCallCheck$1 = function classCallCheck(instance, Constructor) {
  34869. if (!(instance instanceof Constructor)) {
  34870. throw new TypeError("Cannot call a class as a function");
  34871. }
  34872. };
  34873. var createClass$1 = function () {
  34874. function defineProperties(target, props) {
  34875. for (var i = 0; i < props.length; i++) {
  34876. var descriptor = props[i];
  34877. descriptor.enumerable = descriptor.enumerable || false;
  34878. descriptor.configurable = true;
  34879. if ("value" in descriptor) descriptor.writable = true;
  34880. Object.defineProperty(target, descriptor.key, descriptor);
  34881. }
  34882. }
  34883. return function (Constructor, protoProps, staticProps) {
  34884. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  34885. if (staticProps) defineProperties(Constructor, staticProps);
  34886. return Constructor;
  34887. };
  34888. }();
  34889. var get$1 = function get(object, property, receiver) {
  34890. if (object === null) object = Function.prototype;
  34891. var desc = Object.getOwnPropertyDescriptor(object, property);
  34892. if (desc === undefined) {
  34893. var parent = Object.getPrototypeOf(object);
  34894. if (parent === null) {
  34895. return undefined;
  34896. } else {
  34897. return get(parent, property, receiver);
  34898. }
  34899. } else if ("value" in desc) {
  34900. return desc.value;
  34901. } else {
  34902. var getter = desc.get;
  34903. if (getter === undefined) {
  34904. return undefined;
  34905. }
  34906. return getter.call(receiver);
  34907. }
  34908. };
  34909. var inherits$1 = function inherits(subClass, superClass) {
  34910. if (typeof superClass !== "function" && superClass !== null) {
  34911. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  34912. }
  34913. subClass.prototype = Object.create(superClass && superClass.prototype, {
  34914. constructor: {
  34915. value: subClass,
  34916. enumerable: false,
  34917. writable: true,
  34918. configurable: true
  34919. }
  34920. });
  34921. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  34922. };
  34923. var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) {
  34924. if (!self) {
  34925. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  34926. }
  34927. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  34928. };
  34929. var slicedToArray = function () {
  34930. function sliceIterator(arr, i) {
  34931. var _arr = [];
  34932. var _n = true;
  34933. var _d = false;
  34934. var _e = undefined;
  34935. try {
  34936. for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
  34937. _arr.push(_s.value);
  34938. if (i && _arr.length === i) break;
  34939. }
  34940. } catch (err) {
  34941. _d = true;
  34942. _e = err;
  34943. } finally {
  34944. try {
  34945. if (!_n && _i["return"]) _i["return"]();
  34946. } finally {
  34947. if (_d) throw _e;
  34948. }
  34949. }
  34950. return _arr;
  34951. }
  34952. return function (arr, i) {
  34953. if (Array.isArray(arr)) {
  34954. return arr;
  34955. } else if (Symbol.iterator in Object(arr)) {
  34956. return sliceIterator(arr, i);
  34957. } else {
  34958. throw new TypeError("Invalid attempt to destructure non-iterable instance");
  34959. }
  34960. };
  34961. }();
  34962. /**
  34963. * @file playlist-loader.js
  34964. *
  34965. * A state machine that manages the loading, caching, and updating of
  34966. * M3U8 playlists.
  34967. *
  34968. */
  34969. var mergeOptions$1 = videojs$1.mergeOptions,
  34970. EventTarget$1 = videojs$1.EventTarget,
  34971. log$1 = videojs$1.log;
  34972. /**
  34973. * Loops through all supported media groups in master and calls the provided
  34974. * callback for each group
  34975. *
  34976. * @param {Object} master
  34977. * The parsed master manifest object
  34978. * @param {Function} callback
  34979. * Callback to call for each media group
  34980. */
  34981. var forEachMediaGroup = function forEachMediaGroup(master, callback) {
  34982. ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
  34983. for (var groupKey in master.mediaGroups[mediaType]) {
  34984. for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
  34985. var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
  34986. callback(mediaProperties, mediaType, groupKey, labelKey);
  34987. }
  34988. }
  34989. });
  34990. };
  34991. /**
  34992. * Returns a new array of segments that is the result of merging
  34993. * properties from an older list of segments onto an updated
  34994. * list. No properties on the updated playlist will be overridden.
  34995. *
  34996. * @param {Array} original the outdated list of segments
  34997. * @param {Array} update the updated list of segments
  34998. * @param {Number=} offset the index of the first update
  34999. * segment in the original segment list. For non-live playlists,
  35000. * this should always be zero and does not need to be
  35001. * specified. For live playlists, it should be the difference
  35002. * between the media sequence numbers in the original and updated
  35003. * playlists.
  35004. * @return a list of merged segment objects
  35005. */
  35006. var updateSegments = function updateSegments(original, update, offset) {
  35007. var result = update.slice();
  35008. offset = offset || 0;
  35009. var length = Math.min(original.length, update.length + offset);
  35010. for (var i = offset; i < length; i++) {
  35011. result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
  35012. }
  35013. return result;
  35014. };
  35015. var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
  35016. if (!segment.resolvedUri) {
  35017. segment.resolvedUri = resolveUrl$1(baseUri, segment.uri);
  35018. }
  35019. if (segment.key && !segment.key.resolvedUri) {
  35020. segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri);
  35021. }
  35022. if (segment.map && !segment.map.resolvedUri) {
  35023. segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri);
  35024. }
  35025. };
  35026. /**
  35027. * Returns a new master playlist that is the result of merging an
  35028. * updated media playlist into the original version. If the
  35029. * updated media playlist does not match any of the playlist
  35030. * entries in the original master playlist, null is returned.
  35031. *
  35032. * @param {Object} master a parsed master M3U8 object
  35033. * @param {Object} media a parsed media M3U8 object
  35034. * @return {Object} a new object that represents the original
  35035. * master playlist with the updated media playlist merged in, or
  35036. * null if the merge produced no change.
  35037. */
  35038. var updateMaster = function updateMaster(master, media) {
  35039. var result = mergeOptions$1(master, {});
  35040. var playlist = result.playlists[media.uri];
  35041. if (!playlist) {
  35042. return null;
  35043. } // consider the playlist unchanged if the number of segments is equal, the media
  35044. // sequence number is unchanged, and this playlist hasn't become the end of the playlist
  35045. if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) {
  35046. return null;
  35047. }
  35048. var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists
  35049. if (playlist.segments) {
  35050. mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
  35051. } // resolve any segment URIs to prevent us from having to do it later
  35052. mergedPlaylist.segments.forEach(function (segment) {
  35053. resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
  35054. }); // TODO Right now in the playlists array there are two references to each playlist, one
  35055. // that is referenced by index, and one by URI. The index reference may no longer be
  35056. // necessary.
  35057. for (var i = 0; i < result.playlists.length; i++) {
  35058. if (result.playlists[i].uri === media.uri) {
  35059. result.playlists[i] = mergedPlaylist;
  35060. }
  35061. }
  35062. result.playlists[media.uri] = mergedPlaylist;
  35063. return result;
  35064. };
  35065. var setupMediaPlaylists = function setupMediaPlaylists(master) {
  35066. // setup by-URI lookups and resolve media playlist URIs
  35067. var i = master.playlists.length;
  35068. while (i--) {
  35069. var playlist = master.playlists[i];
  35070. master.playlists[playlist.uri] = playlist;
  35071. playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri);
  35072. playlist.id = i;
  35073. if (!playlist.attributes) {
  35074. // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
  35075. // BANDWIDTH attribute, we can play the stream without it. This means a poorly
  35076. // formatted master playlist may not have an attribute list. An attributes
  35077. // property is added here to prevent undefined references when we encounter
  35078. // this scenario.
  35079. playlist.attributes = {};
  35080. log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
  35081. }
  35082. }
  35083. };
  35084. var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
  35085. forEachMediaGroup(master, function (properties) {
  35086. if (properties.uri) {
  35087. properties.resolvedUri = resolveUrl$1(master.uri, properties.uri);
  35088. }
  35089. });
  35090. };
  35091. /**
  35092. * Calculates the time to wait before refreshing a live playlist
  35093. *
  35094. * @param {Object} media
  35095. * The current media
  35096. * @param {Boolean} update
  35097. * True if there were any updates from the last refresh, false otherwise
  35098. * @return {Number}
  35099. * The time in ms to wait before refreshing the live playlist
  35100. */
  35101. var refreshDelay = function refreshDelay(media, update) {
  35102. var lastSegment = media.segments[media.segments.length - 1];
  35103. var delay = void 0;
  35104. if (update && lastSegment && lastSegment.duration) {
  35105. delay = lastSegment.duration * 1000;
  35106. } else {
  35107. // if the playlist is unchanged since the last reload or last segment duration
  35108. // cannot be determined, try again after half the target duration
  35109. delay = (media.targetDuration || 10) * 500;
  35110. }
  35111. return delay;
  35112. };
  35113. /**
  35114. * Load a playlist from a remote location
  35115. *
  35116. * @class PlaylistLoader
  35117. * @extends Stream
  35118. * @param {String} srcUrl the url to start with
  35119. * @param {Boolean} withCredentials the withCredentials xhr option
  35120. * @constructor
  35121. */
  35122. var PlaylistLoader = function (_EventTarget) {
  35123. inherits$1(PlaylistLoader, _EventTarget);
  35124. function PlaylistLoader(srcUrl, hls) {
  35125. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  35126. classCallCheck$1(this, PlaylistLoader);
  35127. var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
  35128. var _options$withCredenti = options.withCredentials,
  35129. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  35130. _options$handleManife = options.handleManifestRedirects,
  35131. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  35132. _this.srcUrl = srcUrl;
  35133. _this.hls_ = hls;
  35134. _this.withCredentials = withCredentials;
  35135. _this.handleManifestRedirects = handleManifestRedirects;
  35136. var hlsOptions = hls.options_;
  35137. _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || [];
  35138. _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || [];
  35139. if (!_this.srcUrl) {
  35140. throw new Error('A non-empty playlist URL is required');
  35141. } // initialize the loader state
  35142. _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
  35143. _this.on('mediaupdatetimeout', function () {
  35144. if (_this.state !== 'HAVE_METADATA') {
  35145. // only refresh the media playlist if no other activity is going on
  35146. return;
  35147. }
  35148. _this.state = 'HAVE_CURRENT_METADATA';
  35149. _this.request = _this.hls_.xhr({
  35150. uri: resolveUrl$1(_this.master.uri, _this.media().uri),
  35151. withCredentials: _this.withCredentials
  35152. }, function (error, req) {
  35153. // disposed
  35154. if (!_this.request) {
  35155. return;
  35156. }
  35157. if (error) {
  35158. return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
  35159. }
  35160. _this.haveMetadata(_this.request, _this.media().uri);
  35161. });
  35162. });
  35163. return _this;
  35164. }
  35165. createClass$1(PlaylistLoader, [{
  35166. key: 'playlistRequestError',
  35167. value: function playlistRequestError(xhr, url, startingState) {
  35168. // any in-flight request is now finished
  35169. this.request = null;
  35170. if (startingState) {
  35171. this.state = startingState;
  35172. }
  35173. this.error = {
  35174. playlist: this.master.playlists[url],
  35175. status: xhr.status,
  35176. message: 'HLS playlist request error at URL: ' + url + '.',
  35177. responseText: xhr.responseText,
  35178. code: xhr.status >= 500 ? 4 : 2
  35179. };
  35180. this.trigger('error');
  35181. } // update the playlist loader's state in response to a new or
  35182. // updated playlist.
  35183. }, {
  35184. key: 'haveMetadata',
  35185. value: function haveMetadata(xhr, url) {
  35186. var _this2 = this; // any in-flight request is now finished
  35187. this.request = null;
  35188. this.state = 'HAVE_METADATA';
  35189. var parser = new Parser(); // adding custom tag parsers
  35190. this.customTagParsers.forEach(function (customParser) {
  35191. return parser.addParser(customParser);
  35192. }); // adding custom tag mappers
  35193. this.customTagMappers.forEach(function (mapper) {
  35194. return parser.addTagMapper(mapper);
  35195. });
  35196. parser.push(xhr.responseText);
  35197. parser.end();
  35198. parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make
  35199. // sure that the property is attached to avoid undefined reference errors
  35200. parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master
  35201. var update = updateMaster(this.master, parser.manifest);
  35202. this.targetDuration = parser.manifest.targetDuration;
  35203. if (update) {
  35204. this.master = update;
  35205. this.media_ = this.master.playlists[parser.manifest.uri];
  35206. } else {
  35207. this.trigger('playlistunchanged');
  35208. } // refresh live playlists after a target duration passes
  35209. if (!this.media().endList) {
  35210. window$1.clearTimeout(this.mediaUpdateTimeout);
  35211. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  35212. _this2.trigger('mediaupdatetimeout');
  35213. }, refreshDelay(this.media(), !!update));
  35214. }
  35215. this.trigger('loadedplaylist');
  35216. }
  35217. /**
  35218. * Abort any outstanding work and clean up.
  35219. */
  35220. }, {
  35221. key: 'dispose',
  35222. value: function dispose() {
  35223. this.stopRequest();
  35224. window$1.clearTimeout(this.mediaUpdateTimeout);
  35225. window$1.clearTimeout(this.finalRenditionTimeout);
  35226. }
  35227. }, {
  35228. key: 'stopRequest',
  35229. value: function stopRequest() {
  35230. if (this.request) {
  35231. var oldRequest = this.request;
  35232. this.request = null;
  35233. oldRequest.onreadystatechange = null;
  35234. oldRequest.abort();
  35235. }
  35236. }
  35237. /**
  35238. * When called without any arguments, returns the currently
  35239. * active media playlist. When called with a single argument,
  35240. * triggers the playlist loader to asynchronously switch to the
  35241. * specified media playlist. Calling this method while the
  35242. * loader is in the HAVE_NOTHING causes an error to be emitted
  35243. * but otherwise has no effect.
  35244. *
  35245. * @param {Object=} playlist the parsed media playlist
  35246. * object to switch to
  35247. * @param {Boolean=} is this the last available playlist
  35248. *
  35249. * @return {Playlist} the current loaded media
  35250. */
  35251. }, {
  35252. key: 'media',
  35253. value: function media(playlist, isFinalRendition) {
  35254. var _this3 = this; // getter
  35255. if (!playlist) {
  35256. return this.media_;
  35257. } // setter
  35258. if (this.state === 'HAVE_NOTHING') {
  35259. throw new Error('Cannot switch media playlist from ' + this.state);
  35260. } // find the playlist object if the target playlist has been
  35261. // specified by URI
  35262. if (typeof playlist === 'string') {
  35263. if (!this.master.playlists[playlist]) {
  35264. throw new Error('Unknown playlist URI: ' + playlist);
  35265. }
  35266. playlist = this.master.playlists[playlist];
  35267. }
  35268. window$1.clearTimeout(this.finalRenditionTimeout);
  35269. if (isFinalRendition) {
  35270. var delay = playlist.targetDuration / 2 * 1000 || 5 * 1000;
  35271. this.finalRenditionTimeout = window$1.setTimeout(this.media.bind(this, playlist, false), delay);
  35272. return;
  35273. }
  35274. var startingState = this.state;
  35275. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately
  35276. if (this.master.playlists[playlist.uri].endList) {
  35277. // abort outstanding playlist requests
  35278. if (this.request) {
  35279. this.request.onreadystatechange = null;
  35280. this.request.abort();
  35281. this.request = null;
  35282. }
  35283. this.state = 'HAVE_METADATA';
  35284. this.media_ = playlist; // trigger media change if the active media has been updated
  35285. if (mediaChange) {
  35286. this.trigger('mediachanging');
  35287. this.trigger('mediachange');
  35288. }
  35289. return;
  35290. } // switching to the active playlist is a no-op
  35291. if (!mediaChange) {
  35292. return;
  35293. }
  35294. this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
  35295. if (this.request) {
  35296. if (playlist.resolvedUri === this.request.url) {
  35297. // requesting to switch to the same playlist multiple times
  35298. // has no effect after the first
  35299. return;
  35300. }
  35301. this.request.onreadystatechange = null;
  35302. this.request.abort();
  35303. this.request = null;
  35304. } // request the new playlist
  35305. if (this.media_) {
  35306. this.trigger('mediachanging');
  35307. }
  35308. this.request = this.hls_.xhr({
  35309. uri: playlist.resolvedUri,
  35310. withCredentials: this.withCredentials
  35311. }, function (error, req) {
  35312. // disposed
  35313. if (!_this3.request) {
  35314. return;
  35315. }
  35316. playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req);
  35317. if (error) {
  35318. return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
  35319. }
  35320. _this3.haveMetadata(req, playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  35321. if (startingState === 'HAVE_MASTER') {
  35322. _this3.trigger('loadedmetadata');
  35323. } else {
  35324. _this3.trigger('mediachange');
  35325. }
  35326. });
  35327. }
  35328. /**
  35329. * pause loading of the playlist
  35330. */
  35331. }, {
  35332. key: 'pause',
  35333. value: function pause() {
  35334. this.stopRequest();
  35335. window$1.clearTimeout(this.mediaUpdateTimeout);
  35336. if (this.state === 'HAVE_NOTHING') {
  35337. // If we pause the loader before any data has been retrieved, its as if we never
  35338. // started, so reset to an unstarted state.
  35339. this.started = false;
  35340. } // Need to restore state now that no activity is happening
  35341. if (this.state === 'SWITCHING_MEDIA') {
  35342. // if the loader was in the process of switching media, it should either return to
  35343. // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
  35344. // playlist yet. This is determined by the existence of loader.media_
  35345. if (this.media_) {
  35346. this.state = 'HAVE_METADATA';
  35347. } else {
  35348. this.state = 'HAVE_MASTER';
  35349. }
  35350. } else if (this.state === 'HAVE_CURRENT_METADATA') {
  35351. this.state = 'HAVE_METADATA';
  35352. }
  35353. }
  35354. /**
  35355. * start loading of the playlist
  35356. */
  35357. }, {
  35358. key: 'load',
  35359. value: function load(isFinalRendition) {
  35360. var _this4 = this;
  35361. window$1.clearTimeout(this.mediaUpdateTimeout);
  35362. var media = this.media();
  35363. if (isFinalRendition) {
  35364. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  35365. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  35366. return _this4.load();
  35367. }, delay);
  35368. return;
  35369. }
  35370. if (!this.started) {
  35371. this.start();
  35372. return;
  35373. }
  35374. if (media && !media.endList) {
  35375. this.trigger('mediaupdatetimeout');
  35376. } else {
  35377. this.trigger('loadedplaylist');
  35378. }
  35379. }
  35380. /**
  35381. * start loading of the playlist
  35382. */
  35383. }, {
  35384. key: 'start',
  35385. value: function start() {
  35386. var _this5 = this;
  35387. this.started = true; // request the specified URL
  35388. this.request = this.hls_.xhr({
  35389. uri: this.srcUrl,
  35390. withCredentials: this.withCredentials
  35391. }, function (error, req) {
  35392. // disposed
  35393. if (!_this5.request) {
  35394. return;
  35395. } // clear the loader's request reference
  35396. _this5.request = null;
  35397. if (error) {
  35398. _this5.error = {
  35399. status: req.status,
  35400. message: 'HLS playlist request error at URL: ' + _this5.srcUrl + '.',
  35401. responseText: req.responseText,
  35402. // MEDIA_ERR_NETWORK
  35403. code: 2
  35404. };
  35405. if (_this5.state === 'HAVE_NOTHING') {
  35406. _this5.started = false;
  35407. }
  35408. return _this5.trigger('error');
  35409. }
  35410. var parser = new Parser(); // adding custom tag parsers
  35411. _this5.customTagParsers.forEach(function (customParser) {
  35412. return parser.addParser(customParser);
  35413. }); // adding custom tag mappers
  35414. _this5.customTagMappers.forEach(function (mapper) {
  35415. return parser.addTagMapper(mapper);
  35416. });
  35417. parser.push(req.responseText);
  35418. parser.end();
  35419. _this5.state = 'HAVE_MASTER';
  35420. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  35421. parser.manifest.uri = _this5.srcUrl; // loaded a master playlist
  35422. if (parser.manifest.playlists) {
  35423. _this5.master = parser.manifest;
  35424. setupMediaPlaylists(_this5.master);
  35425. resolveMediaGroupUris(_this5.master);
  35426. _this5.trigger('loadedplaylist');
  35427. if (!_this5.request) {
  35428. // no media playlist was specifically selected so start
  35429. // from the first listed one
  35430. _this5.media(parser.manifest.playlists[0]);
  35431. }
  35432. return;
  35433. } // loaded a media playlist
  35434. // infer a master playlist if none was previously requested
  35435. _this5.master = {
  35436. mediaGroups: {
  35437. 'AUDIO': {},
  35438. 'VIDEO': {},
  35439. 'CLOSED-CAPTIONS': {},
  35440. 'SUBTITLES': {}
  35441. },
  35442. uri: window$1.location.href,
  35443. playlists: [{
  35444. uri: _this5.srcUrl,
  35445. id: 0,
  35446. resolvedUri: _this5.srcUrl,
  35447. // m3u8-parser does not attach an attributes property to media playlists so make
  35448. // sure that the property is attached to avoid undefined reference errors
  35449. attributes: {}
  35450. }]
  35451. };
  35452. _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
  35453. _this5.haveMetadata(req, _this5.srcUrl);
  35454. return _this5.trigger('loadedmetadata');
  35455. });
  35456. }
  35457. }]);
  35458. return PlaylistLoader;
  35459. }(EventTarget$1);
  35460. /**
  35461. * @file playlist.js
  35462. *
  35463. * Playlist related utilities.
  35464. */
  35465. var createTimeRange = videojs$1.createTimeRange;
  35466. /**
  35467. * walk backward until we find a duration we can use
  35468. * or return a failure
  35469. *
  35470. * @param {Playlist} playlist the playlist to walk through
  35471. * @param {Number} endSequence the mediaSequence to stop walking on
  35472. */
  35473. var backwardDuration = function backwardDuration(playlist, endSequence) {
  35474. var result = 0;
  35475. var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
  35476. // the interval, use it
  35477. var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
  35478. // information that is earlier than endSequence
  35479. if (segment) {
  35480. if (typeof segment.start !== 'undefined') {
  35481. return {
  35482. result: segment.start,
  35483. precise: true
  35484. };
  35485. }
  35486. if (typeof segment.end !== 'undefined') {
  35487. return {
  35488. result: segment.end - segment.duration,
  35489. precise: true
  35490. };
  35491. }
  35492. }
  35493. while (i--) {
  35494. segment = playlist.segments[i];
  35495. if (typeof segment.end !== 'undefined') {
  35496. return {
  35497. result: result + segment.end,
  35498. precise: true
  35499. };
  35500. }
  35501. result += segment.duration;
  35502. if (typeof segment.start !== 'undefined') {
  35503. return {
  35504. result: result + segment.start,
  35505. precise: true
  35506. };
  35507. }
  35508. }
  35509. return {
  35510. result: result,
  35511. precise: false
  35512. };
  35513. };
  35514. /**
  35515. * walk forward until we find a duration we can use
  35516. * or return a failure
  35517. *
  35518. * @param {Playlist} playlist the playlist to walk through
  35519. * @param {Number} endSequence the mediaSequence to stop walking on
  35520. */
  35521. var forwardDuration = function forwardDuration(playlist, endSequence) {
  35522. var result = 0;
  35523. var segment = void 0;
  35524. var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
  35525. // information
  35526. for (; i < playlist.segments.length; i++) {
  35527. segment = playlist.segments[i];
  35528. if (typeof segment.start !== 'undefined') {
  35529. return {
  35530. result: segment.start - result,
  35531. precise: true
  35532. };
  35533. }
  35534. result += segment.duration;
  35535. if (typeof segment.end !== 'undefined') {
  35536. return {
  35537. result: segment.end - result,
  35538. precise: true
  35539. };
  35540. }
  35541. } // indicate we didn't find a useful duration estimate
  35542. return {
  35543. result: -1,
  35544. precise: false
  35545. };
  35546. };
  35547. /**
  35548. * Calculate the media duration from the segments associated with a
  35549. * playlist. The duration of a subinterval of the available segments
  35550. * may be calculated by specifying an end index.
  35551. *
  35552. * @param {Object} playlist a media playlist object
  35553. * @param {Number=} endSequence an exclusive upper boundary
  35554. * for the playlist. Defaults to playlist length.
  35555. * @param {Number} expired the amount of time that has dropped
  35556. * off the front of the playlist in a live scenario
  35557. * @return {Number} the duration between the first available segment
  35558. * and end index.
  35559. */
  35560. var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
  35561. var backward = void 0;
  35562. var forward = void 0;
  35563. if (typeof endSequence === 'undefined') {
  35564. endSequence = playlist.mediaSequence + playlist.segments.length;
  35565. }
  35566. if (endSequence < playlist.mediaSequence) {
  35567. return 0;
  35568. } // do a backward walk to estimate the duration
  35569. backward = backwardDuration(playlist, endSequence);
  35570. if (backward.precise) {
  35571. // if we were able to base our duration estimate on timing
  35572. // information provided directly from the Media Source, return
  35573. // it
  35574. return backward.result;
  35575. } // walk forward to see if a precise duration estimate can be made
  35576. // that way
  35577. forward = forwardDuration(playlist, endSequence);
  35578. if (forward.precise) {
  35579. // we found a segment that has been buffered and so it's
  35580. // position is known precisely
  35581. return forward.result;
  35582. } // return the less-precise, playlist-based duration estimate
  35583. return backward.result + expired;
  35584. };
  35585. /**
  35586. * Calculates the duration of a playlist. If a start and end index
  35587. * are specified, the duration will be for the subset of the media
  35588. * timeline between those two indices. The total duration for live
  35589. * playlists is always Infinity.
  35590. *
  35591. * @param {Object} playlist a media playlist object
  35592. * @param {Number=} endSequence an exclusive upper
  35593. * boundary for the playlist. Defaults to the playlist media
  35594. * sequence number plus its length.
  35595. * @param {Number=} expired the amount of time that has
  35596. * dropped off the front of the playlist in a live scenario
  35597. * @return {Number} the duration between the start index and end
  35598. * index.
  35599. */
  35600. var duration = function duration(playlist, endSequence, expired) {
  35601. if (!playlist) {
  35602. return 0;
  35603. }
  35604. if (typeof expired !== 'number') {
  35605. expired = 0;
  35606. } // if a slice of the total duration is not requested, use
  35607. // playlist-level duration indicators when they're present
  35608. if (typeof endSequence === 'undefined') {
  35609. // if present, use the duration specified in the playlist
  35610. if (playlist.totalDuration) {
  35611. return playlist.totalDuration;
  35612. } // duration should be Infinity for live playlists
  35613. if (!playlist.endList) {
  35614. return window$1.Infinity;
  35615. }
  35616. } // calculate the total duration based on the segment durations
  35617. return intervalDuration(playlist, endSequence, expired);
  35618. };
  35619. /**
  35620. * Calculate the time between two indexes in the current playlist
  35621. * neight the start- nor the end-index need to be within the current
  35622. * playlist in which case, the targetDuration of the playlist is used
  35623. * to approximate the durations of the segments
  35624. *
  35625. * @param {Object} playlist a media playlist object
  35626. * @param {Number} startIndex
  35627. * @param {Number} endIndex
  35628. * @return {Number} the number of seconds between startIndex and endIndex
  35629. */
  35630. var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
  35631. var durations = 0;
  35632. if (startIndex > endIndex) {
  35633. var _ref = [endIndex, startIndex];
  35634. startIndex = _ref[0];
  35635. endIndex = _ref[1];
  35636. }
  35637. if (startIndex < 0) {
  35638. for (var i = startIndex; i < Math.min(0, endIndex); i++) {
  35639. durations += playlist.targetDuration;
  35640. }
  35641. startIndex = 0;
  35642. }
  35643. for (var _i = startIndex; _i < endIndex; _i++) {
  35644. durations += playlist.segments[_i].duration;
  35645. }
  35646. return durations;
  35647. };
  35648. /**
  35649. * Determines the media index of the segment corresponding to the safe edge of the live
  35650. * window which is the duration of the last segment plus 2 target durations from the end
  35651. * of the playlist.
  35652. *
  35653. * @param {Object} playlist
  35654. * a media playlist object
  35655. * @return {Number}
  35656. * The media index of the segment at the safe live point. 0 if there is no "safe"
  35657. * point.
  35658. * @function safeLiveIndex
  35659. */
  35660. var safeLiveIndex = function safeLiveIndex(playlist) {
  35661. if (!playlist.segments.length) {
  35662. return 0;
  35663. }
  35664. var i = playlist.segments.length - 1;
  35665. var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
  35666. var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
  35667. while (i--) {
  35668. distanceFromEnd += playlist.segments[i].duration;
  35669. if (distanceFromEnd >= safeDistance) {
  35670. break;
  35671. }
  35672. }
  35673. return Math.max(0, i);
  35674. };
  35675. /**
  35676. * Calculates the playlist end time
  35677. *
  35678. * @param {Object} playlist a media playlist object
  35679. * @param {Number=} expired the amount of time that has
  35680. * dropped off the front of the playlist in a live scenario
  35681. * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
  35682. * playlist end calculation should consider the safe live end
  35683. * (truncate the playlist end by three segments). This is normally
  35684. * used for calculating the end of the playlist's seekable range.
  35685. * @returns {Number} the end time of playlist
  35686. * @function playlistEnd
  35687. */
  35688. var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
  35689. if (!playlist || !playlist.segments) {
  35690. return null;
  35691. }
  35692. if (playlist.endList) {
  35693. return duration(playlist);
  35694. }
  35695. if (expired === null) {
  35696. return null;
  35697. }
  35698. expired = expired || 0;
  35699. var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
  35700. return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
  35701. };
  35702. /**
  35703. * Calculates the interval of time that is currently seekable in a
  35704. * playlist. The returned time ranges are relative to the earliest
  35705. * moment in the specified playlist that is still available. A full
  35706. * seekable implementation for live streams would need to offset
  35707. * these values by the duration of content that has expired from the
  35708. * stream.
  35709. *
  35710. * @param {Object} playlist a media playlist object
  35711. * dropped off the front of the playlist in a live scenario
  35712. * @param {Number=} expired the amount of time that has
  35713. * dropped off the front of the playlist in a live scenario
  35714. * @return {TimeRanges} the periods of time that are valid targets
  35715. * for seeking
  35716. */
  35717. var seekable = function seekable(playlist, expired) {
  35718. var useSafeLiveEnd = true;
  35719. var seekableStart = expired || 0;
  35720. var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
  35721. if (seekableEnd === null) {
  35722. return createTimeRange();
  35723. }
  35724. return createTimeRange(seekableStart, seekableEnd);
  35725. };
  35726. var isWholeNumber = function isWholeNumber(num) {
  35727. return num - Math.floor(num) === 0;
  35728. };
  35729. var roundSignificantDigit = function roundSignificantDigit(increment, num) {
  35730. // If we have a whole number, just add 1 to it
  35731. if (isWholeNumber(num)) {
  35732. return num + increment * 0.1;
  35733. }
  35734. var numDecimalDigits = num.toString().split('.')[1].length;
  35735. for (var i = 1; i <= numDecimalDigits; i++) {
  35736. var scale = Math.pow(10, i);
  35737. var temp = num * scale;
  35738. if (isWholeNumber(temp) || i === numDecimalDigits) {
  35739. return (temp + increment) / scale;
  35740. }
  35741. }
  35742. };
  35743. var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
  35744. var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
  35745. /**
  35746. * Determine the index and estimated starting time of the segment that
  35747. * contains a specified playback position in a media playlist.
  35748. *
  35749. * @param {Object} playlist the media playlist to query
  35750. * @param {Number} currentTime The number of seconds since the earliest
  35751. * possible position to determine the containing segment for
  35752. * @param {Number} startIndex
  35753. * @param {Number} startTime
  35754. * @return {Object}
  35755. */
  35756. var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
  35757. var i = void 0;
  35758. var segment = void 0;
  35759. var numSegments = playlist.segments.length;
  35760. var time = currentTime - startTime;
  35761. if (time < 0) {
  35762. // Walk backward from startIndex in the playlist, adding durations
  35763. // until we find a segment that contains `time` and return it
  35764. if (startIndex > 0) {
  35765. for (i = startIndex - 1; i >= 0; i--) {
  35766. segment = playlist.segments[i];
  35767. time += floorLeastSignificantDigit(segment.duration);
  35768. if (time > 0) {
  35769. return {
  35770. mediaIndex: i,
  35771. startTime: startTime - sumDurations(playlist, startIndex, i)
  35772. };
  35773. }
  35774. }
  35775. } // We were unable to find a good segment within the playlist
  35776. // so select the first segment
  35777. return {
  35778. mediaIndex: 0,
  35779. startTime: currentTime
  35780. };
  35781. } // When startIndex is negative, we first walk forward to first segment
  35782. // adding target durations. If we "run out of time" before getting to
  35783. // the first segment, return the first segment
  35784. if (startIndex < 0) {
  35785. for (i = startIndex; i < 0; i++) {
  35786. time -= playlist.targetDuration;
  35787. if (time < 0) {
  35788. return {
  35789. mediaIndex: 0,
  35790. startTime: currentTime
  35791. };
  35792. }
  35793. }
  35794. startIndex = 0;
  35795. } // Walk forward from startIndex in the playlist, subtracting durations
  35796. // until we find a segment that contains `time` and return it
  35797. for (i = startIndex; i < numSegments; i++) {
  35798. segment = playlist.segments[i];
  35799. time -= ceilLeastSignificantDigit(segment.duration);
  35800. if (time < 0) {
  35801. return {
  35802. mediaIndex: i,
  35803. startTime: startTime + sumDurations(playlist, startIndex, i)
  35804. };
  35805. }
  35806. } // We are out of possible candidates so load the last one...
  35807. return {
  35808. mediaIndex: numSegments - 1,
  35809. startTime: currentTime
  35810. };
  35811. };
  35812. /**
  35813. * Check whether the playlist is blacklisted or not.
  35814. *
  35815. * @param {Object} playlist the media playlist object
  35816. * @return {boolean} whether the playlist is blacklisted or not
  35817. * @function isBlacklisted
  35818. */
  35819. var isBlacklisted = function isBlacklisted(playlist) {
  35820. return playlist.excludeUntil && playlist.excludeUntil > Date.now();
  35821. };
  35822. /**
  35823. * Check whether the playlist is compatible with current playback configuration or has
  35824. * been blacklisted permanently for being incompatible.
  35825. *
  35826. * @param {Object} playlist the media playlist object
  35827. * @return {boolean} whether the playlist is incompatible or not
  35828. * @function isIncompatible
  35829. */
  35830. var isIncompatible = function isIncompatible(playlist) {
  35831. return playlist.excludeUntil && playlist.excludeUntil === Infinity;
  35832. };
  35833. /**
  35834. * Check whether the playlist is enabled or not.
  35835. *
  35836. * @param {Object} playlist the media playlist object
  35837. * @return {boolean} whether the playlist is enabled or not
  35838. * @function isEnabled
  35839. */
  35840. var isEnabled = function isEnabled(playlist) {
  35841. var blacklisted = isBlacklisted(playlist);
  35842. return !playlist.disabled && !blacklisted;
  35843. };
  35844. /**
  35845. * Check whether the playlist has been manually disabled through the representations api.
  35846. *
  35847. * @param {Object} playlist the media playlist object
  35848. * @return {boolean} whether the playlist is disabled manually or not
  35849. * @function isDisabled
  35850. */
  35851. var isDisabled = function isDisabled(playlist) {
  35852. return playlist.disabled;
  35853. };
  35854. /**
  35855. * Returns whether the current playlist is an AES encrypted HLS stream
  35856. *
  35857. * @return {Boolean} true if it's an AES encrypted HLS stream
  35858. */
  35859. var isAes = function isAes(media) {
  35860. for (var i = 0; i < media.segments.length; i++) {
  35861. if (media.segments[i].key) {
  35862. return true;
  35863. }
  35864. }
  35865. return false;
  35866. };
  35867. /**
  35868. * Returns whether the current playlist contains fMP4
  35869. *
  35870. * @return {Boolean} true if the playlist contains fMP4
  35871. */
  35872. var isFmp4 = function isFmp4(media) {
  35873. for (var i = 0; i < media.segments.length; i++) {
  35874. if (media.segments[i].map) {
  35875. return true;
  35876. }
  35877. }
  35878. return false;
  35879. };
  35880. /**
  35881. * Checks if the playlist has a value for the specified attribute
  35882. *
  35883. * @param {String} attr
  35884. * Attribute to check for
  35885. * @param {Object} playlist
  35886. * The media playlist object
  35887. * @return {Boolean}
  35888. * Whether the playlist contains a value for the attribute or not
  35889. * @function hasAttribute
  35890. */
  35891. var hasAttribute = function hasAttribute(attr, playlist) {
  35892. return playlist.attributes && playlist.attributes[attr];
  35893. };
  35894. /**
  35895. * Estimates the time required to complete a segment download from the specified playlist
  35896. *
  35897. * @param {Number} segmentDuration
  35898. * Duration of requested segment
  35899. * @param {Number} bandwidth
  35900. * Current measured bandwidth of the player
  35901. * @param {Object} playlist
  35902. * The media playlist object
  35903. * @param {Number=} bytesReceived
  35904. * Number of bytes already received for the request. Defaults to 0
  35905. * @return {Number|NaN}
  35906. * The estimated time to request the segment. NaN if bandwidth information for
  35907. * the given playlist is unavailable
  35908. * @function estimateSegmentRequestTime
  35909. */
  35910. var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
  35911. var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  35912. if (!hasAttribute('BANDWIDTH', playlist)) {
  35913. return NaN;
  35914. }
  35915. var size = segmentDuration * playlist.attributes.BANDWIDTH;
  35916. return (size - bytesReceived * 8) / bandwidth;
  35917. };
  35918. /*
  35919. * Returns whether the current playlist is the lowest rendition
  35920. *
  35921. * @return {Boolean} true if on lowest rendition
  35922. */
  35923. var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
  35924. if (master.playlists.length === 1) {
  35925. return true;
  35926. }
  35927. var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
  35928. return master.playlists.filter(function (playlist) {
  35929. if (!isEnabled(playlist)) {
  35930. return false;
  35931. }
  35932. return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
  35933. }).length === 0;
  35934. }; // exports
  35935. var Playlist = {
  35936. duration: duration,
  35937. seekable: seekable,
  35938. safeLiveIndex: safeLiveIndex,
  35939. getMediaInfoForTime: getMediaInfoForTime,
  35940. isEnabled: isEnabled,
  35941. isDisabled: isDisabled,
  35942. isBlacklisted: isBlacklisted,
  35943. isIncompatible: isIncompatible,
  35944. playlistEnd: playlistEnd,
  35945. isAes: isAes,
  35946. isFmp4: isFmp4,
  35947. hasAttribute: hasAttribute,
  35948. estimateSegmentRequestTime: estimateSegmentRequestTime,
  35949. isLowestEnabledRendition: isLowestEnabledRendition
  35950. };
  35951. /**
  35952. * @file xhr.js
  35953. */
  35954. var videojsXHR = videojs$1.xhr,
  35955. mergeOptions$1$1 = videojs$1.mergeOptions;
  35956. var xhrFactory = function xhrFactory() {
  35957. var xhr = function XhrFunction(options, callback) {
  35958. // Add a default timeout for all hls requests
  35959. options = mergeOptions$1$1({
  35960. timeout: 45e3
  35961. }, options); // Allow an optional user-specified function to modify the option
  35962. // object before we construct the xhr request
  35963. var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
  35964. if (beforeRequest && typeof beforeRequest === 'function') {
  35965. var newOptions = beforeRequest(options);
  35966. if (newOptions) {
  35967. options = newOptions;
  35968. }
  35969. }
  35970. var request = videojsXHR(options, function (error, response) {
  35971. var reqResponse = request.response;
  35972. if (!error && reqResponse) {
  35973. request.responseTime = Date.now();
  35974. request.roundTripTime = request.responseTime - request.requestTime;
  35975. request.bytesReceived = reqResponse.byteLength || reqResponse.length;
  35976. if (!request.bandwidth) {
  35977. request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
  35978. }
  35979. }
  35980. if (response.headers) {
  35981. request.responseHeaders = response.headers;
  35982. } // videojs.xhr now uses a specific code on the error
  35983. // object to signal that a request has timed out instead
  35984. // of setting a boolean on the request object
  35985. if (error && error.code === 'ETIMEDOUT') {
  35986. request.timedout = true;
  35987. } // videojs.xhr no longer considers status codes outside of 200 and 0
  35988. // (for file uris) to be errors, but the old XHR did, so emulate that
  35989. // behavior. Status 206 may be used in response to byterange requests.
  35990. if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
  35991. error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
  35992. }
  35993. callback(error, request);
  35994. });
  35995. var originalAbort = request.abort;
  35996. request.abort = function () {
  35997. request.aborted = true;
  35998. return originalAbort.apply(request, arguments);
  35999. };
  36000. request.uri = options.uri;
  36001. request.requestTime = Date.now();
  36002. return request;
  36003. };
  36004. return xhr;
  36005. };
  36006. /**
  36007. * Turns segment byterange into a string suitable for use in
  36008. * HTTP Range requests
  36009. *
  36010. * @param {Object} byterange - an object with two values defining the start and end
  36011. * of a byte-range
  36012. */
  36013. var byterangeStr = function byterangeStr(byterange) {
  36014. var byterangeStart = void 0;
  36015. var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range
  36016. // header uses inclusive ranges
  36017. byterangeEnd = byterange.offset + byterange.length - 1;
  36018. byterangeStart = byterange.offset;
  36019. return 'bytes=' + byterangeStart + '-' + byterangeEnd;
  36020. };
  36021. /**
  36022. * Defines headers for use in the xhr request for a particular segment.
  36023. *
  36024. * @param {Object} segment - a simplified copy of the segmentInfo object
  36025. * from SegmentLoader
  36026. */
  36027. var segmentXhrHeaders = function segmentXhrHeaders(segment) {
  36028. var headers = {};
  36029. if (segment.byterange) {
  36030. headers.Range = byterangeStr(segment.byterange);
  36031. }
  36032. return headers;
  36033. };
  36034. /**
  36035. * @file bin-utils.js
  36036. */
  36037. /**
  36038. * convert a TimeRange to text
  36039. *
  36040. * @param {TimeRange} range the timerange to use for conversion
  36041. * @param {Number} i the iterator on the range to convert
  36042. */
  36043. var textRange = function textRange(range, i) {
  36044. return range.start(i) + '-' + range.end(i);
  36045. };
  36046. /**
  36047. * format a number as hex string
  36048. *
  36049. * @param {Number} e The number
  36050. * @param {Number} i the iterator
  36051. */
  36052. var formatHexString = function formatHexString(e, i) {
  36053. var value = e.toString(16);
  36054. return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
  36055. };
  36056. var formatAsciiString = function formatAsciiString(e) {
  36057. if (e >= 0x20 && e < 0x7e) {
  36058. return String.fromCharCode(e);
  36059. }
  36060. return '.';
  36061. };
  36062. /**
  36063. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  36064. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  36065. *
  36066. * @param {Object} message
  36067. * Object of properties and values to send to the web worker
  36068. * @return {Object}
  36069. * Modified message with TypedArray values expanded
  36070. * @function createTransferableMessage
  36071. */
  36072. var createTransferableMessage = function createTransferableMessage(message) {
  36073. var transferable = {};
  36074. Object.keys(message).forEach(function (key) {
  36075. var value = message[key];
  36076. if (ArrayBuffer.isView(value)) {
  36077. transferable[key] = {
  36078. bytes: value.buffer,
  36079. byteOffset: value.byteOffset,
  36080. byteLength: value.byteLength
  36081. };
  36082. } else {
  36083. transferable[key] = value;
  36084. }
  36085. });
  36086. return transferable;
  36087. };
  36088. /**
  36089. * Returns a unique string identifier for a media initialization
  36090. * segment.
  36091. */
  36092. var initSegmentId = function initSegmentId(initSegment) {
  36093. var byterange = initSegment.byterange || {
  36094. length: Infinity,
  36095. offset: 0
  36096. };
  36097. return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
  36098. };
  36099. /**
  36100. * Returns a unique string identifier for a media segment key.
  36101. */
  36102. var segmentKeyId = function segmentKeyId(key) {
  36103. return key.resolvedUri;
  36104. };
  36105. /**
  36106. * utils to help dump binary data to the console
  36107. */
  36108. var hexDump = function hexDump(data) {
  36109. var bytes = Array.prototype.slice.call(data);
  36110. var step = 16;
  36111. var result = '';
  36112. var hex = void 0;
  36113. var ascii = void 0;
  36114. for (var j = 0; j < bytes.length / step; j++) {
  36115. hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
  36116. ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
  36117. result += hex + ' ' + ascii + '\n';
  36118. }
  36119. return result;
  36120. };
  36121. var tagDump = function tagDump(_ref) {
  36122. var bytes = _ref.bytes;
  36123. return hexDump(bytes);
  36124. };
  36125. var textRanges = function textRanges(ranges) {
  36126. var result = '';
  36127. var i = void 0;
  36128. for (i = 0; i < ranges.length; i++) {
  36129. result += textRange(ranges, i) + ' ';
  36130. }
  36131. return result;
  36132. };
  36133. var utils$1 =
  36134. /*#__PURE__*/
  36135. Object.freeze({
  36136. createTransferableMessage: createTransferableMessage,
  36137. initSegmentId: initSegmentId,
  36138. segmentKeyId: segmentKeyId,
  36139. hexDump: hexDump,
  36140. tagDump: tagDump,
  36141. textRanges: textRanges
  36142. }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
  36143. // Add 25% to the segment duration to account for small discrepencies in segment timing.
  36144. // 25% was arbitrarily chosen, and may need to be refined over time.
  36145. var SEGMENT_END_FUDGE_PERCENT = 0.25;
  36146. /**
  36147. * Converts a player time (any time that can be gotten/set from player.currentTime(),
  36148. * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
  36149. * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
  36150. *
  36151. * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
  36152. * point" (a point where we have a mapping from program time to player time, with player
  36153. * time being the post transmux start of the segment).
  36154. *
  36155. * For more details, see [this doc](../../docs/program-time-from-player-time.md).
  36156. *
  36157. * @param {Number} playerTime the player time
  36158. * @param {Object} segment the segment which contains the player time
  36159. * @return {Date} program time
  36160. */
  36161. var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
  36162. if (!segment.dateTimeObject) {
  36163. // Can't convert without an "anchor point" for the program time (i.e., a time that can
  36164. // be used to map the start of a segment with a real world time).
  36165. return null;
  36166. }
  36167. var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
  36168. var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
  36169. var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
  36170. var offsetFromSegmentStart = playerTime - startOfSegment;
  36171. return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
  36172. };
  36173. var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
  36174. return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
  36175. };
  36176. /**
  36177. * Finds a segment that contains the time requested given as an ISO-8601 string. The
  36178. * returned segment might be an estimate or an accurate match.
  36179. *
  36180. * @param {String} programTime The ISO-8601 programTime to find a match for
  36181. * @param {Object} playlist A playlist object to search within
  36182. */
  36183. var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
  36184. // Assumptions:
  36185. // - verifyProgramDateTimeTags has already been run
  36186. // - live streams have been started
  36187. var dateTimeObject = void 0;
  36188. try {
  36189. dateTimeObject = new Date(programTime);
  36190. } catch (e) {
  36191. return null;
  36192. }
  36193. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  36194. return null;
  36195. }
  36196. var segment = playlist.segments[0];
  36197. if (dateTimeObject < segment.dateTimeObject) {
  36198. // Requested time is before stream start.
  36199. return null;
  36200. }
  36201. for (var i = 0; i < playlist.segments.length - 1; i++) {
  36202. segment = playlist.segments[i];
  36203. var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
  36204. if (dateTimeObject < nextSegmentStart) {
  36205. break;
  36206. }
  36207. }
  36208. var lastSegment = playlist.segments[playlist.segments.length - 1];
  36209. var lastSegmentStart = lastSegment.dateTimeObject;
  36210. var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
  36211. var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
  36212. if (dateTimeObject > lastSegmentEnd) {
  36213. // Beyond the end of the stream, or our best guess of the end of the stream.
  36214. return null;
  36215. }
  36216. if (dateTimeObject > lastSegmentStart) {
  36217. segment = lastSegment;
  36218. }
  36219. return {
  36220. segment: segment,
  36221. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
  36222. // Although, given that all segments have accurate date time objects, the segment
  36223. // selected should be accurate, unless the video has been transmuxed at some point
  36224. // (determined by the presence of the videoTimingInfo object), the segment's "player
  36225. // time" (the start time in the player) can't be considered accurate.
  36226. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  36227. };
  36228. };
  36229. /**
  36230. * Finds a segment that contains the given player time(in seconds).
  36231. *
  36232. * @param {Number} time The player time to find a match for
  36233. * @param {Object} playlist A playlist object to search within
  36234. */
  36235. var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
  36236. // Assumptions:
  36237. // - there will always be a segment.duration
  36238. // - we can start from zero
  36239. // - segments are in time order
  36240. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  36241. return null;
  36242. }
  36243. var segmentEnd = 0;
  36244. var segment = void 0;
  36245. for (var i = 0; i < playlist.segments.length; i++) {
  36246. segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
  36247. // should contain the most accurate values we have for the segment's player times.
  36248. //
  36249. // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
  36250. // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
  36251. // calculate an end value.
  36252. segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
  36253. if (time <= segmentEnd) {
  36254. break;
  36255. }
  36256. }
  36257. var lastSegment = playlist.segments[playlist.segments.length - 1];
  36258. if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
  36259. // The time requested is beyond the stream end.
  36260. return null;
  36261. }
  36262. if (time > segmentEnd) {
  36263. // The time is within or beyond the last segment.
  36264. //
  36265. // Check to see if the time is beyond a reasonable guess of the end of the stream.
  36266. if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
  36267. // Technically, because the duration value is only an estimate, the time may still
  36268. // exist in the last segment, however, there isn't enough information to make even
  36269. // a reasonable estimate.
  36270. return null;
  36271. }
  36272. segment = lastSegment;
  36273. }
  36274. return {
  36275. segment: segment,
  36276. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
  36277. // Because videoTimingInfo is only set after transmux, it is the only way to get
  36278. // accurate timing values.
  36279. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  36280. };
  36281. };
  36282. /**
  36283. * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
  36284. * If the offset returned is positive, the programTime occurs after the
  36285. * comparisonTimestamp.
  36286. * If the offset is negative, the programTime occurs before the comparisonTimestamp.
  36287. *
  36288. * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against
  36289. * @param {String} programTime The programTime as an ISO-8601 string
  36290. * @return {Number} offset
  36291. */
  36292. var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
  36293. var segmentDateTime = void 0;
  36294. var programDateTime = void 0;
  36295. try {
  36296. segmentDateTime = new Date(comparisonTimeStamp);
  36297. programDateTime = new Date(programTime);
  36298. } catch (e) {// TODO handle error
  36299. }
  36300. var segmentTimeEpoch = segmentDateTime.getTime();
  36301. var programTimeEpoch = programDateTime.getTime();
  36302. return (programTimeEpoch - segmentTimeEpoch) / 1000;
  36303. };
  36304. /**
  36305. * Checks that all segments in this playlist have programDateTime tags.
  36306. *
  36307. * @param {Object} playlist A playlist object
  36308. */
  36309. var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
  36310. if (!playlist.segments || playlist.segments.length === 0) {
  36311. return false;
  36312. }
  36313. for (var i = 0; i < playlist.segments.length; i++) {
  36314. var segment = playlist.segments[i];
  36315. if (!segment.dateTimeObject) {
  36316. return false;
  36317. }
  36318. }
  36319. return true;
  36320. };
  36321. /**
  36322. * Returns the programTime of the media given a playlist and a playerTime.
  36323. * The playlist must have programDateTime tags for a programDateTime tag to be returned.
  36324. * If the segments containing the time requested have not been buffered yet, an estimate
  36325. * may be returned to the callback.
  36326. *
  36327. * @param {Object} args
  36328. * @param {Object} args.playlist A playlist object to search within
  36329. * @param {Number} time A playerTime in seconds
  36330. * @param {Function} callback(err, programTime)
  36331. * @returns {String} err.message A detailed error message
  36332. * @returns {Object} programTime
  36333. * @returns {Number} programTime.mediaSeconds The streamTime in seconds
  36334. * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String
  36335. */
  36336. var getProgramTime = function getProgramTime(_ref) {
  36337. var playlist = _ref.playlist,
  36338. _ref$time = _ref.time,
  36339. time = _ref$time === undefined ? undefined : _ref$time,
  36340. callback = _ref.callback;
  36341. if (!callback) {
  36342. throw new Error('getProgramTime: callback must be provided');
  36343. }
  36344. if (!playlist || time === undefined) {
  36345. return callback({
  36346. message: 'getProgramTime: playlist and time must be provided'
  36347. });
  36348. }
  36349. var matchedSegment = findSegmentForPlayerTime(time, playlist);
  36350. if (!matchedSegment) {
  36351. return callback({
  36352. message: 'valid programTime was not found'
  36353. });
  36354. }
  36355. if (matchedSegment.type === 'estimate') {
  36356. return callback({
  36357. message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
  36358. seekTime: matchedSegment.estimatedStart
  36359. });
  36360. }
  36361. var programTimeObject = {
  36362. mediaSeconds: time
  36363. };
  36364. var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
  36365. if (programTime) {
  36366. programTimeObject.programDateTime = programTime.toISOString();
  36367. }
  36368. return callback(null, programTimeObject);
  36369. };
  36370. /**
  36371. * Seeks in the player to a time that matches the given programTime ISO-8601 string.
  36372. *
  36373. * @param {Object} args
  36374. * @param {String} args.programTime A programTime to seek to as an ISO-8601 String
  36375. * @param {Object} args.playlist A playlist to look within
  36376. * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2.
  36377. * @param {Function} args.seekTo A method to perform a seek
  36378. * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
  36379. * @param {Object} args.tech The tech to seek on
  36380. * @param {Function} args.callback(err, newTime) A callback to return the new time to
  36381. * @returns {String} err.message A detailed error message
  36382. * @returns {Number} newTime The exact time that was seeked to in seconds
  36383. */
  36384. var seekToProgramTime = function seekToProgramTime(_ref2) {
  36385. var programTime = _ref2.programTime,
  36386. playlist = _ref2.playlist,
  36387. _ref2$retryCount = _ref2.retryCount,
  36388. retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount,
  36389. seekTo = _ref2.seekTo,
  36390. _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
  36391. pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek,
  36392. tech = _ref2.tech,
  36393. callback = _ref2.callback;
  36394. if (!callback) {
  36395. throw new Error('seekToProgramTime: callback must be provided');
  36396. }
  36397. if (typeof programTime === 'undefined' || !playlist || !seekTo) {
  36398. return callback({
  36399. message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
  36400. });
  36401. }
  36402. if (!playlist.endList && !tech.hasStarted_) {
  36403. return callback({
  36404. message: 'player must be playing a live stream to start buffering'
  36405. });
  36406. }
  36407. if (!verifyProgramDateTimeTags(playlist)) {
  36408. return callback({
  36409. message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
  36410. });
  36411. }
  36412. var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
  36413. if (!matchedSegment) {
  36414. return callback({
  36415. message: programTime + ' was not found in the stream'
  36416. });
  36417. }
  36418. var segment = matchedSegment.segment;
  36419. var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
  36420. if (matchedSegment.type === 'estimate') {
  36421. // we've run out of retries
  36422. if (retryCount === 0) {
  36423. return callback({
  36424. message: programTime + ' is not buffered yet. Try again'
  36425. });
  36426. }
  36427. seekTo(matchedSegment.estimatedStart + mediaOffset);
  36428. tech.one('seeked', function () {
  36429. seekToProgramTime({
  36430. programTime: programTime,
  36431. playlist: playlist,
  36432. retryCount: retryCount - 1,
  36433. seekTo: seekTo,
  36434. pauseAfterSeek: pauseAfterSeek,
  36435. tech: tech,
  36436. callback: callback
  36437. });
  36438. });
  36439. return;
  36440. } // Since the segment.start value is determined from the buffered end or ending time
  36441. // of the prior segment, the seekToTime doesn't need to account for any transmuxer
  36442. // modifications.
  36443. var seekToTime = segment.start + mediaOffset;
  36444. var seekedCallback = function seekedCallback() {
  36445. return callback(null, tech.currentTime());
  36446. }; // listen for seeked event
  36447. tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
  36448. if (pauseAfterSeek) {
  36449. tech.pause();
  36450. }
  36451. seekTo(seekToTime);
  36452. };
  36453. /**
  36454. * ranges
  36455. *
  36456. * Utilities for working with TimeRanges.
  36457. *
  36458. */
  36459. // Fudge factor to account for TimeRanges rounding
  36460. var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
  36461. // can be misleading because of precision differences or when the current media has poorly
  36462. // aligned audio and video, which can cause values to be slightly off from what you would
  36463. // expect. This value is what we consider to be safe to use in such comparisons to account
  36464. // for these scenarios.
  36465. var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
  36466. var filterRanges = function filterRanges(timeRanges, predicate) {
  36467. var results = [];
  36468. var i = void 0;
  36469. if (timeRanges && timeRanges.length) {
  36470. // Search for ranges that match the predicate
  36471. for (i = 0; i < timeRanges.length; i++) {
  36472. if (predicate(timeRanges.start(i), timeRanges.end(i))) {
  36473. results.push([timeRanges.start(i), timeRanges.end(i)]);
  36474. }
  36475. }
  36476. }
  36477. return videojs$1.createTimeRanges(results);
  36478. };
  36479. /**
  36480. * Attempts to find the buffered TimeRange that contains the specified
  36481. * time.
  36482. * @param {TimeRanges} buffered - the TimeRanges object to query
  36483. * @param {number} time - the time to filter on.
  36484. * @returns {TimeRanges} a new TimeRanges object
  36485. */
  36486. var findRange = function findRange(buffered, time) {
  36487. return filterRanges(buffered, function (start, end) {
  36488. return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
  36489. });
  36490. };
  36491. /**
  36492. * Returns the TimeRanges that begin later than the specified time.
  36493. * @param {TimeRanges} timeRanges - the TimeRanges object to query
  36494. * @param {number} time - the time to filter on.
  36495. * @returns {TimeRanges} a new TimeRanges object.
  36496. */
  36497. var findNextRange = function findNextRange(timeRanges, time) {
  36498. return filterRanges(timeRanges, function (start) {
  36499. return start - TIME_FUDGE_FACTOR >= time;
  36500. });
  36501. };
  36502. /**
  36503. * Returns gaps within a list of TimeRanges
  36504. * @param {TimeRanges} buffered - the TimeRanges object
  36505. * @return {TimeRanges} a TimeRanges object of gaps
  36506. */
  36507. var findGaps = function findGaps(buffered) {
  36508. if (buffered.length < 2) {
  36509. return videojs$1.createTimeRanges();
  36510. }
  36511. var ranges = [];
  36512. for (var i = 1; i < buffered.length; i++) {
  36513. var start = buffered.end(i - 1);
  36514. var end = buffered.start(i);
  36515. ranges.push([start, end]);
  36516. }
  36517. return videojs$1.createTimeRanges(ranges);
  36518. };
  36519. /**
  36520. * Gets a human readable string for a TimeRange
  36521. *
  36522. * @param {TimeRange} range
  36523. * @returns {String} a human readable string
  36524. */
  36525. var printableRange = function printableRange(range) {
  36526. var strArr = [];
  36527. if (!range || !range.length) {
  36528. return '';
  36529. }
  36530. for (var i = 0; i < range.length; i++) {
  36531. strArr.push(range.start(i) + ' => ' + range.end(i));
  36532. }
  36533. return strArr.join(', ');
  36534. };
  36535. /**
  36536. * Calculates the amount of time left in seconds until the player hits the end of the
  36537. * buffer and causes a rebuffer
  36538. *
  36539. * @param {TimeRange} buffered
  36540. * The state of the buffer
  36541. * @param {Numnber} currentTime
  36542. * The current time of the player
  36543. * @param {Number} playbackRate
  36544. * The current playback rate of the player. Defaults to 1.
  36545. * @return {Number}
  36546. * Time until the player has to start rebuffering in seconds.
  36547. * @function timeUntilRebuffer
  36548. */
  36549. var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
  36550. var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
  36551. var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
  36552. return (bufferedEnd - currentTime) / playbackRate;
  36553. };
  36554. /**
  36555. * Converts a TimeRanges object into an array representation
  36556. * @param {TimeRanges} timeRanges
  36557. * @returns {Array}
  36558. */
  36559. var timeRangesToArray = function timeRangesToArray(timeRanges) {
  36560. var timeRangesList = [];
  36561. for (var i = 0; i < timeRanges.length; i++) {
  36562. timeRangesList.push({
  36563. start: timeRanges.start(i),
  36564. end: timeRanges.end(i)
  36565. });
  36566. }
  36567. return timeRangesList;
  36568. };
  36569. /**
  36570. * @file create-text-tracks-if-necessary.js
  36571. */
  36572. /**
  36573. * Create text tracks on video.js if they exist on a segment.
  36574. *
  36575. * @param {Object} sourceBuffer the VSB or FSB
  36576. * @param {Object} mediaSource the HTML media source
  36577. * @param {Object} segment the segment that may contain the text track
  36578. * @private
  36579. */
  36580. var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
  36581. var player = mediaSource.player_; // create an in-band caption track if one is present in the segment
  36582. if (segment.captions && segment.captions.length) {
  36583. if (!sourceBuffer.inbandTextTracks_) {
  36584. sourceBuffer.inbandTextTracks_ = {};
  36585. }
  36586. for (var trackId in segment.captionStreams) {
  36587. if (!sourceBuffer.inbandTextTracks_[trackId]) {
  36588. player.tech_.trigger({
  36589. type: 'usage',
  36590. name: 'hls-608'
  36591. });
  36592. var track = player.textTracks().getTrackById(trackId);
  36593. if (track) {
  36594. // Resuse an existing track with a CC# id because this was
  36595. // very likely created by videojs-contrib-hls from information
  36596. // in the m3u8 for us to use
  36597. sourceBuffer.inbandTextTracks_[trackId] = track;
  36598. } else {
  36599. // Otherwise, create a track with the default `CC#` label and
  36600. // without a language
  36601. sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
  36602. kind: 'captions',
  36603. id: trackId,
  36604. label: trackId
  36605. }, false).track;
  36606. }
  36607. }
  36608. }
  36609. }
  36610. if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
  36611. sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
  36612. kind: 'metadata',
  36613. label: 'Timed Metadata'
  36614. }, false).track;
  36615. sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
  36616. }
  36617. };
  36618. /**
  36619. * @file remove-cues-from-track.js
  36620. */
  36621. /**
  36622. * Remove cues from a track on video.js.
  36623. *
  36624. * @param {Double} start start of where we should remove the cue
  36625. * @param {Double} end end of where the we should remove the cue
  36626. * @param {Object} track the text track to remove the cues from
  36627. * @private
  36628. */
  36629. var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
  36630. var i = void 0;
  36631. var cue = void 0;
  36632. if (!track) {
  36633. return;
  36634. }
  36635. if (!track.cues) {
  36636. return;
  36637. }
  36638. i = track.cues.length;
  36639. while (i--) {
  36640. cue = track.cues[i]; // Remove any overlapping cue
  36641. if (cue.startTime <= end && cue.endTime >= start) {
  36642. track.removeCue(cue);
  36643. }
  36644. }
  36645. };
  36646. /**
  36647. * @file add-text-track-data.js
  36648. */
  36649. /**
  36650. * Define properties on a cue for backwards compatability,
  36651. * but warn the user that the way that they are using it
  36652. * is depricated and will be removed at a later date.
  36653. *
  36654. * @param {Cue} cue the cue to add the properties on
  36655. * @private
  36656. */
  36657. var deprecateOldCue = function deprecateOldCue(cue) {
  36658. Object.defineProperties(cue.frame, {
  36659. id: {
  36660. get: function get() {
  36661. videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
  36662. return cue.value.key;
  36663. }
  36664. },
  36665. value: {
  36666. get: function get() {
  36667. videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
  36668. return cue.value.data;
  36669. }
  36670. },
  36671. privateData: {
  36672. get: function get() {
  36673. videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
  36674. return cue.value.data;
  36675. }
  36676. }
  36677. });
  36678. };
  36679. var durationOfVideo = function durationOfVideo(duration) {
  36680. var dur = void 0;
  36681. if (isNaN(duration) || Math.abs(duration) === Infinity) {
  36682. dur = Number.MAX_VALUE;
  36683. } else {
  36684. dur = duration;
  36685. }
  36686. return dur;
  36687. };
  36688. /**
  36689. * Add text track data to a source handler given the captions and
  36690. * metadata from the buffer.
  36691. *
  36692. * @param {Object} sourceHandler the virtual source buffer
  36693. * @param {Array} captionArray an array of caption data
  36694. * @param {Array} metadataArray an array of meta data
  36695. * @private
  36696. */
  36697. var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
  36698. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  36699. if (captionArray) {
  36700. captionArray.forEach(function (caption) {
  36701. var track = caption.stream;
  36702. this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
  36703. }, sourceHandler);
  36704. }
  36705. if (metadataArray) {
  36706. var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
  36707. metadataArray.forEach(function (metadata) {
  36708. var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
  36709. // ignore this bit of metadata.
  36710. // This likely occurs when you have an non-timed ID3 tag like TIT2,
  36711. // which is the "Title/Songname/Content description" frame
  36712. if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
  36713. return;
  36714. }
  36715. metadata.frames.forEach(function (frame) {
  36716. var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
  36717. cue.frame = frame;
  36718. cue.value = frame;
  36719. deprecateOldCue(cue);
  36720. this.metadataTrack_.addCue(cue);
  36721. }, this);
  36722. }, sourceHandler); // Updating the metadeta cues so that
  36723. // the endTime of each cue is the startTime of the next cue
  36724. // the endTime of last cue is the duration of the video
  36725. if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
  36726. var cues = sourceHandler.metadataTrack_.cues;
  36727. var cuesArray = []; // Create a copy of the TextTrackCueList...
  36728. // ...disregarding cues with a falsey value
  36729. for (var i = 0; i < cues.length; i++) {
  36730. if (cues[i]) {
  36731. cuesArray.push(cues[i]);
  36732. }
  36733. } // Group cues by their startTime value
  36734. var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
  36735. var timeSlot = obj[cue.startTime] || [];
  36736. timeSlot.push(cue);
  36737. obj[cue.startTime] = timeSlot;
  36738. return obj;
  36739. }, {}); // Sort startTimes by ascending order
  36740. var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
  36741. return Number(a) - Number(b);
  36742. }); // Map each cue group's endTime to the next group's startTime
  36743. sortedStartTimes.forEach(function (startTime, idx) {
  36744. var cueGroup = cuesGroupedByStartTime[startTime];
  36745. var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
  36746. cueGroup.forEach(function (cue) {
  36747. cue.endTime = nextTime;
  36748. });
  36749. });
  36750. }
  36751. }
  36752. };
  36753. var win = typeof window !== 'undefined' ? window : {},
  36754. TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
  36755. SCRIPT_TYPE = 'application/javascript',
  36756. BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder,
  36757. URL = win.URL || win.webkitURL || URL && URL.msURL,
  36758. Worker = win.Worker;
  36759. /**
  36760. * Returns a wrapper around Web Worker code that is constructible.
  36761. *
  36762. * @function shimWorker
  36763. *
  36764. * @param { String } filename The name of the file
  36765. * @param { Function } fn Function wrapping the code of the worker
  36766. */
  36767. function shimWorker(filename, fn) {
  36768. return function ShimWorker(forceFallback) {
  36769. var o = this;
  36770. if (!fn) {
  36771. return new Worker(filename);
  36772. } else if (Worker && !forceFallback) {
  36773. // Convert the function's inner code to a string to construct the worker
  36774. var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
  36775. objURL = createSourceObject(source);
  36776. this[TARGET] = new Worker(objURL);
  36777. wrapTerminate(this[TARGET], objURL);
  36778. return this[TARGET];
  36779. } else {
  36780. var selfShim = {
  36781. postMessage: function postMessage(m) {
  36782. if (o.onmessage) {
  36783. setTimeout(function () {
  36784. o.onmessage({
  36785. data: m,
  36786. target: selfShim
  36787. });
  36788. });
  36789. }
  36790. }
  36791. };
  36792. fn.call(selfShim);
  36793. this.postMessage = function (m) {
  36794. setTimeout(function () {
  36795. selfShim.onmessage({
  36796. data: m,
  36797. target: o
  36798. });
  36799. });
  36800. };
  36801. this.isThisThread = true;
  36802. }
  36803. };
  36804. } // Test Worker capabilities
  36805. if (Worker) {
  36806. var testWorker,
  36807. objURL = createSourceObject('self.onmessage = function () {}'),
  36808. testArray = new Uint8Array(1);
  36809. try {
  36810. testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it
  36811. testWorker.postMessage(testArray, [testArray.buffer]);
  36812. } catch (e) {
  36813. Worker = null;
  36814. } finally {
  36815. URL.revokeObjectURL(objURL);
  36816. if (testWorker) {
  36817. testWorker.terminate();
  36818. }
  36819. }
  36820. }
  36821. function createSourceObject(str) {
  36822. try {
  36823. return URL.createObjectURL(new Blob([str], {
  36824. type: SCRIPT_TYPE
  36825. }));
  36826. } catch (e) {
  36827. var blob = new BlobBuilder();
  36828. blob.append(str);
  36829. return URL.createObjectURL(blob.getBlob(type));
  36830. }
  36831. }
  36832. function wrapTerminate(worker, objURL) {
  36833. if (!worker || !objURL) return;
  36834. var term = worker.terminate;
  36835. worker.objURL = objURL;
  36836. worker.terminate = function () {
  36837. if (worker.objURL) URL.revokeObjectURL(worker.objURL);
  36838. term.call(worker);
  36839. };
  36840. }
  36841. var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
  36842. var self = this;
  36843. var transmuxerWorker = function () {
  36844. /**
  36845. * mux.js
  36846. *
  36847. * Copyright (c) Brightcove
  36848. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  36849. *
  36850. * Functions that generate fragmented MP4s suitable for use with Media
  36851. * Source Extensions.
  36852. */
  36853. var UINT32_MAX = Math.pow(2, 32) - 1;
  36854. 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
  36855. (function () {
  36856. var i;
  36857. types = {
  36858. avc1: [],
  36859. // codingname
  36860. avcC: [],
  36861. btrt: [],
  36862. dinf: [],
  36863. dref: [],
  36864. esds: [],
  36865. ftyp: [],
  36866. hdlr: [],
  36867. mdat: [],
  36868. mdhd: [],
  36869. mdia: [],
  36870. mfhd: [],
  36871. minf: [],
  36872. moof: [],
  36873. moov: [],
  36874. mp4a: [],
  36875. // codingname
  36876. mvex: [],
  36877. mvhd: [],
  36878. sdtp: [],
  36879. smhd: [],
  36880. stbl: [],
  36881. stco: [],
  36882. stsc: [],
  36883. stsd: [],
  36884. stsz: [],
  36885. stts: [],
  36886. styp: [],
  36887. tfdt: [],
  36888. tfhd: [],
  36889. traf: [],
  36890. trak: [],
  36891. trun: [],
  36892. trex: [],
  36893. tkhd: [],
  36894. vmhd: []
  36895. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  36896. // don't throw an error
  36897. if (typeof Uint8Array === 'undefined') {
  36898. return;
  36899. }
  36900. for (i in types) {
  36901. if (types.hasOwnProperty(i)) {
  36902. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  36903. }
  36904. }
  36905. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  36906. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  36907. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  36908. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  36909. 0x00, 0x00, 0x00, // flags
  36910. 0x00, 0x00, 0x00, 0x00, // pre_defined
  36911. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  36912. 0x00, 0x00, 0x00, 0x00, // reserved
  36913. 0x00, 0x00, 0x00, 0x00, // reserved
  36914. 0x00, 0x00, 0x00, 0x00, // reserved
  36915. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  36916. ]);
  36917. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  36918. 0x00, 0x00, 0x00, // flags
  36919. 0x00, 0x00, 0x00, 0x00, // pre_defined
  36920. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  36921. 0x00, 0x00, 0x00, 0x00, // reserved
  36922. 0x00, 0x00, 0x00, 0x00, // reserved
  36923. 0x00, 0x00, 0x00, 0x00, // reserved
  36924. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  36925. ]);
  36926. HDLR_TYPES = {
  36927. video: VIDEO_HDLR,
  36928. audio: AUDIO_HDLR
  36929. };
  36930. DREF = new Uint8Array([0x00, // version 0
  36931. 0x00, 0x00, 0x00, // flags
  36932. 0x00, 0x00, 0x00, 0x01, // entry_count
  36933. 0x00, 0x00, 0x00, 0x0c, // entry_size
  36934. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  36935. 0x00, // version 0
  36936. 0x00, 0x00, 0x01 // entry_flags
  36937. ]);
  36938. SMHD = new Uint8Array([0x00, // version
  36939. 0x00, 0x00, 0x00, // flags
  36940. 0x00, 0x00, // balance, 0 means centered
  36941. 0x00, 0x00 // reserved
  36942. ]);
  36943. STCO = new Uint8Array([0x00, // version
  36944. 0x00, 0x00, 0x00, // flags
  36945. 0x00, 0x00, 0x00, 0x00 // entry_count
  36946. ]);
  36947. STSC = STCO;
  36948. STSZ = new Uint8Array([0x00, // version
  36949. 0x00, 0x00, 0x00, // flags
  36950. 0x00, 0x00, 0x00, 0x00, // sample_size
  36951. 0x00, 0x00, 0x00, 0x00 // sample_count
  36952. ]);
  36953. STTS = STCO;
  36954. VMHD = new Uint8Array([0x00, // version
  36955. 0x00, 0x00, 0x01, // flags
  36956. 0x00, 0x00, // graphicsmode
  36957. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  36958. ]);
  36959. })();
  36960. box = function box(type) {
  36961. var payload = [],
  36962. size = 0,
  36963. i,
  36964. result,
  36965. view;
  36966. for (i = 1; i < arguments.length; i++) {
  36967. payload.push(arguments[i]);
  36968. }
  36969. i = payload.length; // calculate the total size we need to allocate
  36970. while (i--) {
  36971. size += payload[i].byteLength;
  36972. }
  36973. result = new Uint8Array(size + 8);
  36974. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  36975. view.setUint32(0, result.byteLength);
  36976. result.set(type, 4); // copy the payload into the result
  36977. for (i = 0, size = 8; i < payload.length; i++) {
  36978. result.set(payload[i], size);
  36979. size += payload[i].byteLength;
  36980. }
  36981. return result;
  36982. };
  36983. dinf = function dinf() {
  36984. return box(types.dinf, box(types.dref, DREF));
  36985. };
  36986. esds = function esds(track) {
  36987. return box(types.esds, new Uint8Array([0x00, // version
  36988. 0x00, 0x00, 0x00, // flags
  36989. // ES_Descriptor
  36990. 0x03, // tag, ES_DescrTag
  36991. 0x19, // length
  36992. 0x00, 0x00, // ES_ID
  36993. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  36994. // DecoderConfigDescriptor
  36995. 0x04, // tag, DecoderConfigDescrTag
  36996. 0x11, // length
  36997. 0x40, // object type
  36998. 0x15, // streamType
  36999. 0x00, 0x06, 0x00, // bufferSizeDB
  37000. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  37001. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  37002. // DecoderSpecificInfo
  37003. 0x05, // tag, DecoderSpecificInfoTag
  37004. 0x02, // length
  37005. // ISO/IEC 14496-3, AudioSpecificConfig
  37006. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  37007. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  37008. ]));
  37009. };
  37010. ftyp = function ftyp() {
  37011. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  37012. };
  37013. hdlr = function hdlr(type) {
  37014. return box(types.hdlr, HDLR_TYPES[type]);
  37015. };
  37016. mdat = function mdat(data) {
  37017. return box(types.mdat, data);
  37018. };
  37019. mdhd = function mdhd(track) {
  37020. var result = new Uint8Array([0x00, // version 0
  37021. 0x00, 0x00, 0x00, // flags
  37022. 0x00, 0x00, 0x00, 0x02, // creation_time
  37023. 0x00, 0x00, 0x00, 0x03, // modification_time
  37024. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  37025. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  37026. 0x55, 0xc4, // 'und' language (undetermined)
  37027. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  37028. // defined. The sample rate can be parsed out of an ADTS header, for
  37029. // instance.
  37030. if (track.samplerate) {
  37031. result[12] = track.samplerate >>> 24 & 0xFF;
  37032. result[13] = track.samplerate >>> 16 & 0xFF;
  37033. result[14] = track.samplerate >>> 8 & 0xFF;
  37034. result[15] = track.samplerate & 0xFF;
  37035. }
  37036. return box(types.mdhd, result);
  37037. };
  37038. mdia = function mdia(track) {
  37039. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  37040. };
  37041. mfhd = function mfhd(sequenceNumber) {
  37042. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  37043. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  37044. ]));
  37045. };
  37046. minf = function minf(track) {
  37047. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  37048. };
  37049. moof = function moof(sequenceNumber, tracks) {
  37050. var trackFragments = [],
  37051. i = tracks.length; // build traf boxes for each track fragment
  37052. while (i--) {
  37053. trackFragments[i] = traf(tracks[i]);
  37054. }
  37055. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  37056. };
  37057. /**
  37058. * Returns a movie box.
  37059. * @param tracks {array} the tracks associated with this movie
  37060. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  37061. */
  37062. moov = function moov(tracks) {
  37063. var i = tracks.length,
  37064. boxes = [];
  37065. while (i--) {
  37066. boxes[i] = trak(tracks[i]);
  37067. }
  37068. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  37069. };
  37070. mvex = function mvex(tracks) {
  37071. var i = tracks.length,
  37072. boxes = [];
  37073. while (i--) {
  37074. boxes[i] = trex(tracks[i]);
  37075. }
  37076. return box.apply(null, [types.mvex].concat(boxes));
  37077. };
  37078. mvhd = function mvhd(duration) {
  37079. var bytes = new Uint8Array([0x00, // version 0
  37080. 0x00, 0x00, 0x00, // flags
  37081. 0x00, 0x00, 0x00, 0x01, // creation_time
  37082. 0x00, 0x00, 0x00, 0x02, // modification_time
  37083. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  37084. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  37085. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  37086. 0x01, 0x00, // 1.0 volume
  37087. 0x00, 0x00, // reserved
  37088. 0x00, 0x00, 0x00, 0x00, // reserved
  37089. 0x00, 0x00, 0x00, 0x00, // reserved
  37090. 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
  37091. 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
  37092. 0xff, 0xff, 0xff, 0xff // next_track_ID
  37093. ]);
  37094. return box(types.mvhd, bytes);
  37095. };
  37096. sdtp = function sdtp(track) {
  37097. var samples = track.samples || [],
  37098. bytes = new Uint8Array(4 + samples.length),
  37099. flags,
  37100. i; // leave the full box header (4 bytes) all zero
  37101. // write the sample table
  37102. for (i = 0; i < samples.length; i++) {
  37103. flags = samples[i].flags;
  37104. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  37105. }
  37106. return box(types.sdtp, bytes);
  37107. };
  37108. stbl = function stbl(track) {
  37109. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  37110. };
  37111. (function () {
  37112. var videoSample, audioSample;
  37113. stsd = function stsd(track) {
  37114. return box(types.stsd, new Uint8Array([0x00, // version 0
  37115. 0x00, 0x00, 0x00, // flags
  37116. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  37117. };
  37118. videoSample = function videoSample(track) {
  37119. var sps = track.sps || [],
  37120. pps = track.pps || [],
  37121. sequenceParameterSets = [],
  37122. pictureParameterSets = [],
  37123. i; // assemble the SPSs
  37124. for (i = 0; i < sps.length; i++) {
  37125. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  37126. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  37127. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  37128. } // assemble the PPSs
  37129. for (i = 0; i < pps.length; i++) {
  37130. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  37131. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  37132. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  37133. }
  37134. return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  37135. 0x00, 0x01, // data_reference_index
  37136. 0x00, 0x00, // pre_defined
  37137. 0x00, 0x00, // reserved
  37138. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  37139. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  37140. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  37141. 0x00, 0x48, 0x00, 0x00, // horizresolution
  37142. 0x00, 0x48, 0x00, 0x00, // vertresolution
  37143. 0x00, 0x00, 0x00, 0x00, // reserved
  37144. 0x00, 0x01, // frame_count
  37145. 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
  37146. 0x00, 0x18, // depth = 24
  37147. 0x11, 0x11 // pre_defined = -1
  37148. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  37149. track.profileIdc, // AVCProfileIndication
  37150. track.profileCompatibility, // profile_compatibility
  37151. track.levelIdc, // AVCLevelIndication
  37152. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  37153. ].concat([sps.length // numOfSequenceParameterSets
  37154. ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
  37155. ]).concat(pictureParameterSets))), // "PPS"
  37156. box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  37157. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  37158. 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
  37159. );
  37160. };
  37161. audioSample = function audioSample(track) {
  37162. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  37163. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  37164. 0x00, 0x01, // data_reference_index
  37165. // AudioSampleEntry, ISO/IEC 14496-12
  37166. 0x00, 0x00, 0x00, 0x00, // reserved
  37167. 0x00, 0x00, 0x00, 0x00, // reserved
  37168. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  37169. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  37170. 0x00, 0x00, // pre_defined
  37171. 0x00, 0x00, // reserved
  37172. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  37173. // MP4AudioSampleEntry, ISO/IEC 14496-14
  37174. ]), esds(track));
  37175. };
  37176. })();
  37177. tkhd = function tkhd(track) {
  37178. var result = new Uint8Array([0x00, // version 0
  37179. 0x00, 0x00, 0x07, // flags
  37180. 0x00, 0x00, 0x00, 0x00, // creation_time
  37181. 0x00, 0x00, 0x00, 0x00, // modification_time
  37182. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  37183. 0x00, 0x00, 0x00, 0x00, // reserved
  37184. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  37185. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  37186. 0x00, 0x00, // layer
  37187. 0x00, 0x00, // alternate_group
  37188. 0x01, 0x00, // non-audio track volume
  37189. 0x00, 0x00, // reserved
  37190. 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
  37191. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  37192. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  37193. ]);
  37194. return box(types.tkhd, result);
  37195. };
  37196. /**
  37197. * Generate a track fragment (traf) box. A traf box collects metadata
  37198. * about tracks in a movie fragment (moof) box.
  37199. */
  37200. traf = function traf(track) {
  37201. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  37202. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  37203. 0x00, 0x00, 0x3a, // flags
  37204. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  37205. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  37206. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  37207. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  37208. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  37209. ]));
  37210. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
  37211. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
  37212. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  37213. 0x00, 0x00, 0x00, // flags
  37214. // baseMediaDecodeTime
  37215. 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
  37216. // the containing moof to the first payload byte of the associated
  37217. // mdat
  37218. dataOffset = 32 + // tfhd
  37219. 20 + // tfdt
  37220. 8 + // traf header
  37221. 16 + // mfhd
  37222. 8 + // moof header
  37223. 8; // mdat header
  37224. // audio tracks require less metadata
  37225. if (track.type === 'audio') {
  37226. trackFragmentRun = trun(track, dataOffset);
  37227. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  37228. } // video tracks should contain an independent and disposable samples
  37229. // box (sdtp)
  37230. // generate one and adjust offsets to match
  37231. sampleDependencyTable = sdtp(track);
  37232. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  37233. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  37234. };
  37235. /**
  37236. * Generate a track box.
  37237. * @param track {object} a track definition
  37238. * @return {Uint8Array} the track box
  37239. */
  37240. trak = function trak(track) {
  37241. track.duration = track.duration || 0xffffffff;
  37242. return box(types.trak, tkhd(track), mdia(track));
  37243. };
  37244. trex = function trex(track) {
  37245. var result = new Uint8Array([0x00, // version 0
  37246. 0x00, 0x00, 0x00, // flags
  37247. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  37248. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  37249. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  37250. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  37251. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  37252. ]); // the last two bytes of default_sample_flags is the sample
  37253. // degradation priority, a hint about the importance of this sample
  37254. // relative to others. Lower the degradation priority for all sample
  37255. // types other than video.
  37256. if (track.type !== 'video') {
  37257. result[result.length - 1] = 0x00;
  37258. }
  37259. return box(types.trex, result);
  37260. };
  37261. (function () {
  37262. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  37263. // duration is present for the first sample, it will be present for
  37264. // all subsequent samples.
  37265. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  37266. trunHeader = function trunHeader(samples, offset) {
  37267. var durationPresent = 0,
  37268. sizePresent = 0,
  37269. flagsPresent = 0,
  37270. compositionTimeOffset = 0; // trun flag constants
  37271. if (samples.length) {
  37272. if (samples[0].duration !== undefined) {
  37273. durationPresent = 0x1;
  37274. }
  37275. if (samples[0].size !== undefined) {
  37276. sizePresent = 0x2;
  37277. }
  37278. if (samples[0].flags !== undefined) {
  37279. flagsPresent = 0x4;
  37280. }
  37281. if (samples[0].compositionTimeOffset !== undefined) {
  37282. compositionTimeOffset = 0x8;
  37283. }
  37284. }
  37285. return [0x00, // version 0
  37286. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  37287. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  37288. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  37289. ];
  37290. };
  37291. videoTrun = function videoTrun(track, offset) {
  37292. var bytes, samples, sample, i;
  37293. samples = track.samples || [];
  37294. offset += 8 + 12 + 16 * samples.length;
  37295. bytes = trunHeader(samples, offset);
  37296. for (i = 0; i < samples.length; i++) {
  37297. sample = samples[i];
  37298. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  37299. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
  37300. 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
  37301. (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
  37302. ]);
  37303. }
  37304. return box(types.trun, new Uint8Array(bytes));
  37305. };
  37306. audioTrun = function audioTrun(track, offset) {
  37307. var bytes, samples, sample, i;
  37308. samples = track.samples || [];
  37309. offset += 8 + 12 + 8 * samples.length;
  37310. bytes = trunHeader(samples, offset);
  37311. for (i = 0; i < samples.length; i++) {
  37312. sample = samples[i];
  37313. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  37314. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
  37315. }
  37316. return box(types.trun, new Uint8Array(bytes));
  37317. };
  37318. trun = function trun(track, offset) {
  37319. if (track.type === 'audio') {
  37320. return audioTrun(track, offset);
  37321. }
  37322. return videoTrun(track, offset);
  37323. };
  37324. })();
  37325. var mp4Generator = {
  37326. ftyp: ftyp,
  37327. mdat: mdat,
  37328. moof: moof,
  37329. moov: moov,
  37330. initSegment: function initSegment(tracks) {
  37331. var fileType = ftyp(),
  37332. movie = moov(tracks),
  37333. result;
  37334. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  37335. result.set(fileType);
  37336. result.set(movie, fileType.byteLength);
  37337. return result;
  37338. }
  37339. };
  37340. /**
  37341. * mux.js
  37342. *
  37343. * Copyright (c) Brightcove
  37344. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  37345. */
  37346. var toUnsigned = function toUnsigned(value) {
  37347. return value >>> 0;
  37348. };
  37349. var bin = {
  37350. toUnsigned: toUnsigned
  37351. };
  37352. var toUnsigned$1 = bin.toUnsigned;
  37353. var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
  37354. _findBox = function findBox(data, path) {
  37355. var results = [],
  37356. i,
  37357. size,
  37358. type,
  37359. end,
  37360. subresults;
  37361. if (!path.length) {
  37362. // short-circuit the search for empty paths
  37363. return null;
  37364. }
  37365. for (i = 0; i < data.byteLength;) {
  37366. size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  37367. type = parseType(data.subarray(i + 4, i + 8));
  37368. end = size > 1 ? i + size : data.byteLength;
  37369. if (type === path[0]) {
  37370. if (path.length === 1) {
  37371. // this is the end of the path and we've found the box we were
  37372. // looking for
  37373. results.push(data.subarray(i + 8, end));
  37374. } else {
  37375. // recursively search for the next box along the path
  37376. subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
  37377. if (subresults.length) {
  37378. results = results.concat(subresults);
  37379. }
  37380. }
  37381. }
  37382. i = end;
  37383. } // we've finished searching all of data
  37384. return results;
  37385. };
  37386. /**
  37387. * Returns the string representation of an ASCII encoded four byte buffer.
  37388. * @param buffer {Uint8Array} a four-byte buffer to translate
  37389. * @return {string} the corresponding string
  37390. */
  37391. parseType = function parseType(buffer) {
  37392. var result = '';
  37393. result += String.fromCharCode(buffer[0]);
  37394. result += String.fromCharCode(buffer[1]);
  37395. result += String.fromCharCode(buffer[2]);
  37396. result += String.fromCharCode(buffer[3]);
  37397. return result;
  37398. };
  37399. /**
  37400. * Parses an MP4 initialization segment and extracts the timescale
  37401. * values for any declared tracks. Timescale values indicate the
  37402. * number of clock ticks per second to assume for time-based values
  37403. * elsewhere in the MP4.
  37404. *
  37405. * To determine the start time of an MP4, you need two pieces of
  37406. * information: the timescale unit and the earliest base media decode
  37407. * time. Multiple timescales can be specified within an MP4 but the
  37408. * base media decode time is always expressed in the timescale from
  37409. * the media header box for the track:
  37410. * ```
  37411. * moov > trak > mdia > mdhd.timescale
  37412. * ```
  37413. * @param init {Uint8Array} the bytes of the init segment
  37414. * @return {object} a hash of track ids to timescale values or null if
  37415. * the init segment is malformed.
  37416. */
  37417. timescale = function timescale(init) {
  37418. var result = {},
  37419. traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
  37420. return traks.reduce(function (result, trak) {
  37421. var tkhd, version, index, id, mdhd;
  37422. tkhd = _findBox(trak, ['tkhd'])[0];
  37423. if (!tkhd) {
  37424. return null;
  37425. }
  37426. version = tkhd[0];
  37427. index = version === 0 ? 12 : 20;
  37428. id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  37429. mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
  37430. if (!mdhd) {
  37431. return null;
  37432. }
  37433. version = mdhd[0];
  37434. index = version === 0 ? 12 : 20;
  37435. result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  37436. return result;
  37437. }, result);
  37438. };
  37439. /**
  37440. * Determine the base media decode start time, in seconds, for an MP4
  37441. * fragment. If multiple fragments are specified, the earliest time is
  37442. * returned.
  37443. *
  37444. * The base media decode time can be parsed from track fragment
  37445. * metadata:
  37446. * ```
  37447. * moof > traf > tfdt.baseMediaDecodeTime
  37448. * ```
  37449. * It requires the timescale value from the mdhd to interpret.
  37450. *
  37451. * @param timescale {object} a hash of track ids to timescale values.
  37452. * @return {number} the earliest base media decode start time for the
  37453. * fragment, in seconds
  37454. */
  37455. startTime = function startTime(timescale, fragment) {
  37456. var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
  37457. trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
  37458. baseTimes = [].concat.apply([], trafs.map(function (traf) {
  37459. return _findBox(traf, ['tfhd']).map(function (tfhd) {
  37460. var id, scale, baseTime; // get the track id from the tfhd
  37461. id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  37462. scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  37463. baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
  37464. var version, result;
  37465. version = tfdt[0];
  37466. result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
  37467. if (version === 1) {
  37468. result *= Math.pow(2, 32);
  37469. result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
  37470. }
  37471. return result;
  37472. })[0];
  37473. baseTime = baseTime || Infinity; // convert base time to seconds
  37474. return baseTime / scale;
  37475. });
  37476. })); // return the minimum
  37477. result = Math.min.apply(null, baseTimes);
  37478. return isFinite(result) ? result : 0;
  37479. };
  37480. /**
  37481. * Find the trackIds of the video tracks in this source.
  37482. * Found by parsing the Handler Reference and Track Header Boxes:
  37483. * moov > trak > mdia > hdlr
  37484. * moov > trak > tkhd
  37485. *
  37486. * @param {Uint8Array} init - The bytes of the init segment for this source
  37487. * @return {Number[]} A list of trackIds
  37488. *
  37489. * @see ISO-BMFF-12/2015, Section 8.4.3
  37490. **/
  37491. getVideoTrackIds = function getVideoTrackIds(init) {
  37492. var traks = _findBox(init, ['moov', 'trak']);
  37493. var videoTrackIds = [];
  37494. traks.forEach(function (trak) {
  37495. var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
  37496. var tkhds = _findBox(trak, ['tkhd']);
  37497. hdlrs.forEach(function (hdlr, index) {
  37498. var handlerType = parseType(hdlr.subarray(8, 12));
  37499. var tkhd = tkhds[index];
  37500. var view;
  37501. var version;
  37502. var trackId;
  37503. if (handlerType === 'vide') {
  37504. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  37505. version = view.getUint8(0);
  37506. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  37507. videoTrackIds.push(trackId);
  37508. }
  37509. });
  37510. });
  37511. return videoTrackIds;
  37512. };
  37513. var probe = {
  37514. findBox: _findBox,
  37515. parseType: parseType,
  37516. timescale: timescale,
  37517. startTime: startTime,
  37518. videoTrackIds: getVideoTrackIds
  37519. };
  37520. /**
  37521. * mux.js
  37522. *
  37523. * Copyright (c) Brightcove
  37524. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  37525. *
  37526. * A lightweight readable stream implemention that handles event dispatching.
  37527. * Objects that inherit from streams should call init in their constructors.
  37528. */
  37529. var Stream = function Stream() {
  37530. this.init = function () {
  37531. var listeners = {};
  37532. /**
  37533. * Add a listener for a specified event type.
  37534. * @param type {string} the event name
  37535. * @param listener {function} the callback to be invoked when an event of
  37536. * the specified type occurs
  37537. */
  37538. this.on = function (type, listener) {
  37539. if (!listeners[type]) {
  37540. listeners[type] = [];
  37541. }
  37542. listeners[type] = listeners[type].concat(listener);
  37543. };
  37544. /**
  37545. * Remove a listener for a specified event type.
  37546. * @param type {string} the event name
  37547. * @param listener {function} a function previously registered for this
  37548. * type of event through `on`
  37549. */
  37550. this.off = function (type, listener) {
  37551. var index;
  37552. if (!listeners[type]) {
  37553. return false;
  37554. }
  37555. index = listeners[type].indexOf(listener);
  37556. listeners[type] = listeners[type].slice();
  37557. listeners[type].splice(index, 1);
  37558. return index > -1;
  37559. };
  37560. /**
  37561. * Trigger an event of the specified type on this stream. Any additional
  37562. * arguments to this function are passed as parameters to event listeners.
  37563. * @param type {string} the event name
  37564. */
  37565. this.trigger = function (type) {
  37566. var callbacks, i, length, args;
  37567. callbacks = listeners[type];
  37568. if (!callbacks) {
  37569. return;
  37570. } // Slicing the arguments on every invocation of this method
  37571. // can add a significant amount of overhead. Avoid the
  37572. // intermediate object creation for the common case of a
  37573. // single callback argument
  37574. if (arguments.length === 2) {
  37575. length = callbacks.length;
  37576. for (i = 0; i < length; ++i) {
  37577. callbacks[i].call(this, arguments[1]);
  37578. }
  37579. } else {
  37580. args = [];
  37581. i = arguments.length;
  37582. for (i = 1; i < arguments.length; ++i) {
  37583. args.push(arguments[i]);
  37584. }
  37585. length = callbacks.length;
  37586. for (i = 0; i < length; ++i) {
  37587. callbacks[i].apply(this, args);
  37588. }
  37589. }
  37590. };
  37591. /**
  37592. * Destroys the stream and cleans up.
  37593. */
  37594. this.dispose = function () {
  37595. listeners = {};
  37596. };
  37597. };
  37598. };
  37599. /**
  37600. * Forwards all `data` events on this stream to the destination stream. The
  37601. * destination stream should provide a method `push` to receive the data
  37602. * events as they arrive.
  37603. * @param destination {stream} the stream that will receive all `data` events
  37604. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  37605. * when the current stream emits a 'done' event
  37606. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  37607. */
  37608. Stream.prototype.pipe = function (destination) {
  37609. this.on('data', function (data) {
  37610. destination.push(data);
  37611. });
  37612. this.on('done', function (flushSource) {
  37613. destination.flush(flushSource);
  37614. });
  37615. return destination;
  37616. }; // Default stream functions that are expected to be overridden to perform
  37617. // actual work. These are provided by the prototype as a sort of no-op
  37618. // implementation so that we don't have to check for their existence in the
  37619. // `pipe` function above.
  37620. Stream.prototype.push = function (data) {
  37621. this.trigger('data', data);
  37622. };
  37623. Stream.prototype.flush = function (flushSource) {
  37624. this.trigger('done', flushSource);
  37625. };
  37626. var stream = Stream;
  37627. /**
  37628. * mux.js
  37629. *
  37630. * Copyright (c) Brightcove
  37631. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  37632. */
  37633. // Convert an array of nal units into an array of frames with each frame being
  37634. // composed of the nal units that make up that frame
  37635. // Also keep track of cummulative data about the frame from the nal units such
  37636. // as the frame duration, starting pts, etc.
  37637. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  37638. var i,
  37639. currentNal,
  37640. currentFrame = [],
  37641. frames = [];
  37642. currentFrame.byteLength = 0;
  37643. for (i = 0; i < nalUnits.length; i++) {
  37644. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  37645. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  37646. // Since the very first nal unit is expected to be an AUD
  37647. // only push to the frames array when currentFrame is not empty
  37648. if (currentFrame.length) {
  37649. currentFrame.duration = currentNal.dts - currentFrame.dts;
  37650. frames.push(currentFrame);
  37651. }
  37652. currentFrame = [currentNal];
  37653. currentFrame.byteLength = currentNal.data.byteLength;
  37654. currentFrame.pts = currentNal.pts;
  37655. currentFrame.dts = currentNal.dts;
  37656. } else {
  37657. // Specifically flag key frames for ease of use later
  37658. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  37659. currentFrame.keyFrame = true;
  37660. }
  37661. currentFrame.duration = currentNal.dts - currentFrame.dts;
  37662. currentFrame.byteLength += currentNal.data.byteLength;
  37663. currentFrame.push(currentNal);
  37664. }
  37665. } // For the last frame, use the duration of the previous frame if we
  37666. // have nothing better to go on
  37667. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  37668. currentFrame.duration = frames[frames.length - 1].duration;
  37669. } // Push the final frame
  37670. frames.push(currentFrame);
  37671. return frames;
  37672. }; // Convert an array of frames into an array of Gop with each Gop being composed
  37673. // of the frames that make up that Gop
  37674. // Also keep track of cummulative data about the Gop from the frames such as the
  37675. // Gop duration, starting pts, etc.
  37676. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  37677. var i,
  37678. currentFrame,
  37679. currentGop = [],
  37680. gops = []; // We must pre-set some of the values on the Gop since we
  37681. // keep running totals of these values
  37682. currentGop.byteLength = 0;
  37683. currentGop.nalCount = 0;
  37684. currentGop.duration = 0;
  37685. currentGop.pts = frames[0].pts;
  37686. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  37687. gops.byteLength = 0;
  37688. gops.nalCount = 0;
  37689. gops.duration = 0;
  37690. gops.pts = frames[0].pts;
  37691. gops.dts = frames[0].dts;
  37692. for (i = 0; i < frames.length; i++) {
  37693. currentFrame = frames[i];
  37694. if (currentFrame.keyFrame) {
  37695. // Since the very first frame is expected to be an keyframe
  37696. // only push to the gops array when currentGop is not empty
  37697. if (currentGop.length) {
  37698. gops.push(currentGop);
  37699. gops.byteLength += currentGop.byteLength;
  37700. gops.nalCount += currentGop.nalCount;
  37701. gops.duration += currentGop.duration;
  37702. }
  37703. currentGop = [currentFrame];
  37704. currentGop.nalCount = currentFrame.length;
  37705. currentGop.byteLength = currentFrame.byteLength;
  37706. currentGop.pts = currentFrame.pts;
  37707. currentGop.dts = currentFrame.dts;
  37708. currentGop.duration = currentFrame.duration;
  37709. } else {
  37710. currentGop.duration += currentFrame.duration;
  37711. currentGop.nalCount += currentFrame.length;
  37712. currentGop.byteLength += currentFrame.byteLength;
  37713. currentGop.push(currentFrame);
  37714. }
  37715. }
  37716. if (gops.length && currentGop.duration <= 0) {
  37717. currentGop.duration = gops[gops.length - 1].duration;
  37718. }
  37719. gops.byteLength += currentGop.byteLength;
  37720. gops.nalCount += currentGop.nalCount;
  37721. gops.duration += currentGop.duration; // push the final Gop
  37722. gops.push(currentGop);
  37723. return gops;
  37724. };
  37725. /*
  37726. * Search for the first keyframe in the GOPs and throw away all frames
  37727. * until that keyframe. Then extend the duration of the pulled keyframe
  37728. * and pull the PTS and DTS of the keyframe so that it covers the time
  37729. * range of the frames that were disposed.
  37730. *
  37731. * @param {Array} gops video GOPs
  37732. * @returns {Array} modified video GOPs
  37733. */
  37734. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  37735. var currentGop;
  37736. if (!gops[0][0].keyFrame && gops.length > 1) {
  37737. // Remove the first GOP
  37738. currentGop = gops.shift();
  37739. gops.byteLength -= currentGop.byteLength;
  37740. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  37741. // first gop to cover the time period of the
  37742. // frames we just removed
  37743. gops[0][0].dts = currentGop.dts;
  37744. gops[0][0].pts = currentGop.pts;
  37745. gops[0][0].duration += currentGop.duration;
  37746. }
  37747. return gops;
  37748. };
  37749. /**
  37750. * Default sample object
  37751. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  37752. */
  37753. var createDefaultSample = function createDefaultSample() {
  37754. return {
  37755. size: 0,
  37756. flags: {
  37757. isLeading: 0,
  37758. dependsOn: 1,
  37759. isDependedOn: 0,
  37760. hasRedundancy: 0,
  37761. degradationPriority: 0,
  37762. isNonSyncSample: 1
  37763. }
  37764. };
  37765. };
  37766. /*
  37767. * Collates information from a video frame into an object for eventual
  37768. * entry into an MP4 sample table.
  37769. *
  37770. * @param {Object} frame the video frame
  37771. * @param {Number} dataOffset the byte offset to position the sample
  37772. * @return {Object} object containing sample table info for a frame
  37773. */
  37774. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  37775. var sample = createDefaultSample();
  37776. sample.dataOffset = dataOffset;
  37777. sample.compositionTimeOffset = frame.pts - frame.dts;
  37778. sample.duration = frame.duration;
  37779. sample.size = 4 * frame.length; // Space for nal unit size
  37780. sample.size += frame.byteLength;
  37781. if (frame.keyFrame) {
  37782. sample.flags.dependsOn = 2;
  37783. sample.flags.isNonSyncSample = 0;
  37784. }
  37785. return sample;
  37786. }; // generate the track's sample table from an array of gops
  37787. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  37788. var h,
  37789. i,
  37790. sample,
  37791. currentGop,
  37792. currentFrame,
  37793. dataOffset = baseDataOffset || 0,
  37794. samples = [];
  37795. for (h = 0; h < gops.length; h++) {
  37796. currentGop = gops[h];
  37797. for (i = 0; i < currentGop.length; i++) {
  37798. currentFrame = currentGop[i];
  37799. sample = sampleForFrame(currentFrame, dataOffset);
  37800. dataOffset += sample.size;
  37801. samples.push(sample);
  37802. }
  37803. }
  37804. return samples;
  37805. }; // generate the track's raw mdat data from an array of gops
  37806. var concatenateNalData = function concatenateNalData(gops) {
  37807. var h,
  37808. i,
  37809. j,
  37810. currentGop,
  37811. currentFrame,
  37812. currentNal,
  37813. dataOffset = 0,
  37814. nalsByteLength = gops.byteLength,
  37815. numberOfNals = gops.nalCount,
  37816. totalByteLength = nalsByteLength + 4 * numberOfNals,
  37817. data = new Uint8Array(totalByteLength),
  37818. view = new DataView(data.buffer); // For each Gop..
  37819. for (h = 0; h < gops.length; h++) {
  37820. currentGop = gops[h]; // For each Frame..
  37821. for (i = 0; i < currentGop.length; i++) {
  37822. currentFrame = currentGop[i]; // For each NAL..
  37823. for (j = 0; j < currentFrame.length; j++) {
  37824. currentNal = currentFrame[j];
  37825. view.setUint32(dataOffset, currentNal.data.byteLength);
  37826. dataOffset += 4;
  37827. data.set(currentNal.data, dataOffset);
  37828. dataOffset += currentNal.data.byteLength;
  37829. }
  37830. }
  37831. }
  37832. return data;
  37833. };
  37834. var frameUtils = {
  37835. groupNalsIntoFrames: groupNalsIntoFrames,
  37836. groupFramesIntoGops: groupFramesIntoGops,
  37837. extendFirstKeyFrame: extendFirstKeyFrame,
  37838. generateSampleTable: generateSampleTable,
  37839. concatenateNalData: concatenateNalData
  37840. };
  37841. /**
  37842. * mux.js
  37843. *
  37844. * Copyright (c) Brightcove
  37845. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  37846. */
  37847. var highPrefix = [33, 16, 5, 32, 164, 27];
  37848. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  37849. var zeroFill = function zeroFill(count) {
  37850. var a = [];
  37851. while (count--) {
  37852. a.push(0);
  37853. }
  37854. return a;
  37855. };
  37856. var makeTable = function makeTable(metaTable) {
  37857. return Object.keys(metaTable).reduce(function (obj, key) {
  37858. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  37859. return arr.concat(part);
  37860. }, []));
  37861. return obj;
  37862. }, {});
  37863. }; // Frames-of-silence to use for filling in missing AAC frames
  37864. var coneOfSilence = {
  37865. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  37866. 88200: [highPrefix, [231], zeroFill(170), [56]],
  37867. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  37868. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  37869. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  37870. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  37871. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  37872. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  37873. 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]],
  37874. 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]],
  37875. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  37876. };
  37877. var silence = makeTable(coneOfSilence);
  37878. /**
  37879. * mux.js
  37880. *
  37881. * Copyright (c) Brightcove
  37882. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  37883. */
  37884. var ONE_SECOND_IN_TS = 90000,
  37885. // 90kHz clock
  37886. secondsToVideoTs,
  37887. secondsToAudioTs,
  37888. videoTsToSeconds,
  37889. audioTsToSeconds,
  37890. audioTsToVideoTs,
  37891. videoTsToAudioTs;
  37892. secondsToVideoTs = function secondsToVideoTs(seconds) {
  37893. return seconds * ONE_SECOND_IN_TS;
  37894. };
  37895. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  37896. return seconds * sampleRate;
  37897. };
  37898. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  37899. return timestamp / ONE_SECOND_IN_TS;
  37900. };
  37901. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  37902. return timestamp / sampleRate;
  37903. };
  37904. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  37905. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  37906. };
  37907. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  37908. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  37909. };
  37910. var clock = {
  37911. secondsToVideoTs: secondsToVideoTs,
  37912. secondsToAudioTs: secondsToAudioTs,
  37913. videoTsToSeconds: videoTsToSeconds,
  37914. audioTsToSeconds: audioTsToSeconds,
  37915. audioTsToVideoTs: audioTsToVideoTs,
  37916. videoTsToAudioTs: videoTsToAudioTs
  37917. };
  37918. /**
  37919. * mux.js
  37920. *
  37921. * Copyright (c) Brightcove
  37922. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  37923. */
  37924. var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
  37925. /**
  37926. * Sum the `byteLength` properties of the data in each AAC frame
  37927. */
  37928. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  37929. var i,
  37930. currentObj,
  37931. sum = 0; // sum the byteLength's all each nal unit in the frame
  37932. for (i = 0; i < array.length; i++) {
  37933. currentObj = array[i];
  37934. sum += currentObj.data.byteLength;
  37935. }
  37936. return sum;
  37937. }; // Possibly pad (prefix) the audio track with silence if appending this track
  37938. // would lead to the introduction of a gap in the audio buffer
  37939. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  37940. var baseMediaDecodeTimeTs,
  37941. frameDuration = 0,
  37942. audioGapDuration = 0,
  37943. audioFillFrameCount = 0,
  37944. audioFillDuration = 0,
  37945. silentFrame,
  37946. i;
  37947. if (!frames.length) {
  37948. return;
  37949. }
  37950. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  37951. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
  37952. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  37953. // insert the shortest possible amount (audio gap or audio to video gap)
  37954. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  37955. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  37956. audioFillDuration = audioFillFrameCount * frameDuration;
  37957. } // don't attempt to fill gaps smaller than a single frame or larger
  37958. // than a half second
  37959. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
  37960. return;
  37961. }
  37962. silentFrame = silence[track.samplerate];
  37963. if (!silentFrame) {
  37964. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  37965. // from the content instead
  37966. silentFrame = frames[0].data;
  37967. }
  37968. for (i = 0; i < audioFillFrameCount; i++) {
  37969. frames.splice(i, 0, {
  37970. data: silentFrame
  37971. });
  37972. }
  37973. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  37974. }; // If the audio segment extends before the earliest allowed dts
  37975. // value, remove AAC frames until starts at or after the earliest
  37976. // allowed DTS so that we don't end up with a negative baseMedia-
  37977. // DecodeTime for the audio track
  37978. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  37979. if (track.minSegmentDts >= earliestAllowedDts) {
  37980. return adtsFrames;
  37981. } // We will need to recalculate the earliest segment Dts
  37982. track.minSegmentDts = Infinity;
  37983. return adtsFrames.filter(function (currentFrame) {
  37984. // If this is an allowed frame, keep it and record it's Dts
  37985. if (currentFrame.dts >= earliestAllowedDts) {
  37986. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  37987. track.minSegmentPts = track.minSegmentDts;
  37988. return true;
  37989. } // Otherwise, discard it
  37990. return false;
  37991. });
  37992. }; // generate the track's raw mdat data from an array of frames
  37993. var generateSampleTable$1 = function generateSampleTable(frames) {
  37994. var i,
  37995. currentFrame,
  37996. samples = [];
  37997. for (i = 0; i < frames.length; i++) {
  37998. currentFrame = frames[i];
  37999. samples.push({
  38000. size: currentFrame.data.byteLength,
  38001. duration: 1024 // For AAC audio, all samples contain 1024 samples
  38002. });
  38003. }
  38004. return samples;
  38005. }; // generate the track's sample table from an array of frames
  38006. var concatenateFrameData = function concatenateFrameData(frames) {
  38007. var i,
  38008. currentFrame,
  38009. dataOffset = 0,
  38010. data = new Uint8Array(sumFrameByteLengths(frames));
  38011. for (i = 0; i < frames.length; i++) {
  38012. currentFrame = frames[i];
  38013. data.set(currentFrame.data, dataOffset);
  38014. dataOffset += currentFrame.data.byteLength;
  38015. }
  38016. return data;
  38017. };
  38018. var audioFrameUtils = {
  38019. prefixWithSilence: prefixWithSilence,
  38020. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  38021. generateSampleTable: generateSampleTable$1,
  38022. concatenateFrameData: concatenateFrameData
  38023. };
  38024. /**
  38025. * mux.js
  38026. *
  38027. * Copyright (c) Brightcove
  38028. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  38029. */
  38030. var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
  38031. /**
  38032. * Store information about the start and end of the track and the
  38033. * duration for each frame/sample we process in order to calculate
  38034. * the baseMediaDecodeTime
  38035. */
  38036. var collectDtsInfo = function collectDtsInfo(track, data) {
  38037. if (typeof data.pts === 'number') {
  38038. if (track.timelineStartInfo.pts === undefined) {
  38039. track.timelineStartInfo.pts = data.pts;
  38040. }
  38041. if (track.minSegmentPts === undefined) {
  38042. track.minSegmentPts = data.pts;
  38043. } else {
  38044. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  38045. }
  38046. if (track.maxSegmentPts === undefined) {
  38047. track.maxSegmentPts = data.pts;
  38048. } else {
  38049. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  38050. }
  38051. }
  38052. if (typeof data.dts === 'number') {
  38053. if (track.timelineStartInfo.dts === undefined) {
  38054. track.timelineStartInfo.dts = data.dts;
  38055. }
  38056. if (track.minSegmentDts === undefined) {
  38057. track.minSegmentDts = data.dts;
  38058. } else {
  38059. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  38060. }
  38061. if (track.maxSegmentDts === undefined) {
  38062. track.maxSegmentDts = data.dts;
  38063. } else {
  38064. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  38065. }
  38066. }
  38067. };
  38068. /**
  38069. * Clear values used to calculate the baseMediaDecodeTime between
  38070. * tracks
  38071. */
  38072. var clearDtsInfo = function clearDtsInfo(track) {
  38073. delete track.minSegmentDts;
  38074. delete track.maxSegmentDts;
  38075. delete track.minSegmentPts;
  38076. delete track.maxSegmentPts;
  38077. };
  38078. /**
  38079. * Calculate the track's baseMediaDecodeTime based on the earliest
  38080. * DTS the transmuxer has ever seen and the minimum DTS for the
  38081. * current track
  38082. * @param track {object} track metadata configuration
  38083. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  38084. * in the source; false to adjust the first segment to start at 0.
  38085. */
  38086. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  38087. var baseMediaDecodeTime,
  38088. scale,
  38089. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  38090. if (!keepOriginalTimestamps) {
  38091. minSegmentDts -= track.timelineStartInfo.dts;
  38092. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  38093. // we want the start of the first segment to be placed
  38094. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  38095. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  38096. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  38097. if (track.type === 'audio') {
  38098. // Audio has a different clock equal to the sampling_rate so we need to
  38099. // scale the PTS values into the clock rate of the track
  38100. scale = track.samplerate / ONE_SECOND_IN_TS$2;
  38101. baseMediaDecodeTime *= scale;
  38102. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  38103. }
  38104. return baseMediaDecodeTime;
  38105. };
  38106. var trackDecodeInfo = {
  38107. clearDtsInfo: clearDtsInfo,
  38108. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  38109. collectDtsInfo: collectDtsInfo
  38110. };
  38111. /**
  38112. * mux.js
  38113. *
  38114. * Copyright (c) Brightcove
  38115. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  38116. *
  38117. * Reads in-band caption information from a video elementary
  38118. * stream. Captions must follow the CEA-708 standard for injection
  38119. * into an MPEG-2 transport streams.
  38120. * @see https://en.wikipedia.org/wiki/CEA-708
  38121. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  38122. */
  38123. // Supplemental enhancement information (SEI) NAL units have a
  38124. // payload type field to indicate how they are to be
  38125. // interpreted. CEAS-708 caption content is always transmitted with
  38126. // payload type 0x04.
  38127. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  38128. RBSP_TRAILING_BITS = 128;
  38129. /**
  38130. * Parse a supplemental enhancement information (SEI) NAL unit.
  38131. * Stops parsing once a message of type ITU T T35 has been found.
  38132. *
  38133. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  38134. * @return {object} the parsed SEI payload
  38135. * @see Rec. ITU-T H.264, 7.3.2.3.1
  38136. */
  38137. var parseSei = function parseSei(bytes) {
  38138. var i = 0,
  38139. result = {
  38140. payloadType: -1,
  38141. payloadSize: 0
  38142. },
  38143. payloadType = 0,
  38144. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  38145. while (i < bytes.byteLength) {
  38146. // stop once we have hit the end of the sei_rbsp
  38147. if (bytes[i] === RBSP_TRAILING_BITS) {
  38148. break;
  38149. } // Parse payload type
  38150. while (bytes[i] === 0xFF) {
  38151. payloadType += 255;
  38152. i++;
  38153. }
  38154. payloadType += bytes[i++]; // Parse payload size
  38155. while (bytes[i] === 0xFF) {
  38156. payloadSize += 255;
  38157. i++;
  38158. }
  38159. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  38160. // there can only ever be one caption message in a frame's sei
  38161. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  38162. result.payloadType = payloadType;
  38163. result.payloadSize = payloadSize;
  38164. result.payload = bytes.subarray(i, i + payloadSize);
  38165. break;
  38166. } // skip the payload and parse the next message
  38167. i += payloadSize;
  38168. payloadType = 0;
  38169. payloadSize = 0;
  38170. }
  38171. return result;
  38172. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  38173. var parseUserData = function parseUserData(sei) {
  38174. // itu_t_t35_contry_code must be 181 (United States) for
  38175. // captions
  38176. if (sei.payload[0] !== 181) {
  38177. return null;
  38178. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  38179. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  38180. return null;
  38181. } // the user_identifier should be "GA94" to indicate ATSC1 data
  38182. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  38183. return null;
  38184. } // finally, user_data_type_code should be 0x03 for caption data
  38185. if (sei.payload[7] !== 0x03) {
  38186. return null;
  38187. } // return the user_data_type_structure and strip the trailing
  38188. // marker bits
  38189. return sei.payload.subarray(8, sei.payload.length - 1);
  38190. }; // see CEA-708-D, section 4.4
  38191. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  38192. var results = [],
  38193. i,
  38194. count,
  38195. offset,
  38196. data; // if this is just filler, return immediately
  38197. if (!(userData[0] & 0x40)) {
  38198. return results;
  38199. } // parse out the cc_data_1 and cc_data_2 fields
  38200. count = userData[0] & 0x1f;
  38201. for (i = 0; i < count; i++) {
  38202. offset = i * 3;
  38203. data = {
  38204. type: userData[offset + 2] & 0x03,
  38205. pts: pts
  38206. }; // capture cc data when cc_valid is 1
  38207. if (userData[offset + 2] & 0x04) {
  38208. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  38209. results.push(data);
  38210. }
  38211. }
  38212. return results;
  38213. };
  38214. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  38215. var length = data.byteLength,
  38216. emulationPreventionBytesPositions = [],
  38217. i = 1,
  38218. newLength,
  38219. newData; // Find all `Emulation Prevention Bytes`
  38220. while (i < length - 2) {
  38221. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  38222. emulationPreventionBytesPositions.push(i + 2);
  38223. i += 2;
  38224. } else {
  38225. i++;
  38226. }
  38227. } // If no Emulation Prevention Bytes were found just return the original
  38228. // array
  38229. if (emulationPreventionBytesPositions.length === 0) {
  38230. return data;
  38231. } // Create a new array to hold the NAL unit data
  38232. newLength = length - emulationPreventionBytesPositions.length;
  38233. newData = new Uint8Array(newLength);
  38234. var sourceIndex = 0;
  38235. for (i = 0; i < newLength; sourceIndex++, i++) {
  38236. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  38237. // Skip this byte
  38238. sourceIndex++; // Remove this position index
  38239. emulationPreventionBytesPositions.shift();
  38240. }
  38241. newData[i] = data[sourceIndex];
  38242. }
  38243. return newData;
  38244. }; // exports
  38245. var captionPacketParser = {
  38246. parseSei: parseSei,
  38247. parseUserData: parseUserData,
  38248. parseCaptionPackets: parseCaptionPackets,
  38249. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  38250. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  38251. }; // -----------------
  38252. // Link To Transport
  38253. // -----------------
  38254. var CaptionStream = function CaptionStream() {
  38255. CaptionStream.prototype.init.call(this);
  38256. this.captionPackets_ = [];
  38257. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  38258. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  38259. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  38260. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  38261. ];
  38262. this.reset(); // forward data and done events from CCs to this CaptionStream
  38263. this.ccStreams_.forEach(function (cc) {
  38264. cc.on('data', this.trigger.bind(this, 'data'));
  38265. cc.on('done', this.trigger.bind(this, 'done'));
  38266. }, this);
  38267. };
  38268. CaptionStream.prototype = new stream();
  38269. CaptionStream.prototype.push = function (event) {
  38270. var sei, userData, newCaptionPackets; // only examine SEI NALs
  38271. if (event.nalUnitType !== 'sei_rbsp') {
  38272. return;
  38273. } // parse the sei
  38274. sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
  38275. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  38276. return;
  38277. } // parse out the user data payload
  38278. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  38279. if (!userData) {
  38280. return;
  38281. } // Sometimes, the same segment # will be downloaded twice. To stop the
  38282. // caption data from being processed twice, we track the latest dts we've
  38283. // received and ignore everything with a dts before that. However, since
  38284. // data for a specific dts can be split across packets on either side of
  38285. // a segment boundary, we need to make sure we *don't* ignore the packets
  38286. // from the *next* segment that have dts === this.latestDts_. By constantly
  38287. // tracking the number of packets received with dts === this.latestDts_, we
  38288. // know how many should be ignored once we start receiving duplicates.
  38289. if (event.dts < this.latestDts_) {
  38290. // We've started getting older data, so set the flag.
  38291. this.ignoreNextEqualDts_ = true;
  38292. return;
  38293. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  38294. this.numSameDts_--;
  38295. if (!this.numSameDts_) {
  38296. // We've received the last duplicate packet, time to start processing again
  38297. this.ignoreNextEqualDts_ = false;
  38298. }
  38299. return;
  38300. } // parse out CC data packets and save them for later
  38301. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  38302. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  38303. if (this.latestDts_ !== event.dts) {
  38304. this.numSameDts_ = 0;
  38305. }
  38306. this.numSameDts_++;
  38307. this.latestDts_ = event.dts;
  38308. };
  38309. CaptionStream.prototype.flush = function () {
  38310. // make sure we actually parsed captions before proceeding
  38311. if (!this.captionPackets_.length) {
  38312. this.ccStreams_.forEach(function (cc) {
  38313. cc.flush();
  38314. }, this);
  38315. return;
  38316. } // In Chrome, the Array#sort function is not stable so add a
  38317. // presortIndex that we can use to ensure we get a stable-sort
  38318. this.captionPackets_.forEach(function (elem, idx) {
  38319. elem.presortIndex = idx;
  38320. }); // sort caption byte-pairs based on their PTS values
  38321. this.captionPackets_.sort(function (a, b) {
  38322. if (a.pts === b.pts) {
  38323. return a.presortIndex - b.presortIndex;
  38324. }
  38325. return a.pts - b.pts;
  38326. });
  38327. this.captionPackets_.forEach(function (packet) {
  38328. if (packet.type < 2) {
  38329. // Dispatch packet to the right Cea608Stream
  38330. this.dispatchCea608Packet(packet);
  38331. } // this is where an 'else' would go for a dispatching packets
  38332. // to a theoretical Cea708Stream that handles SERVICEn data
  38333. }, this);
  38334. this.captionPackets_.length = 0;
  38335. this.ccStreams_.forEach(function (cc) {
  38336. cc.flush();
  38337. }, this);
  38338. return;
  38339. };
  38340. CaptionStream.prototype.reset = function () {
  38341. this.latestDts_ = null;
  38342. this.ignoreNextEqualDts_ = false;
  38343. this.numSameDts_ = 0;
  38344. this.activeCea608Channel_ = [null, null];
  38345. this.ccStreams_.forEach(function (ccStream) {
  38346. ccStream.reset();
  38347. });
  38348. }; // From the CEA-608 spec:
  38349. /*
  38350. * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
  38351. * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
  38352. * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
  38353. * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
  38354. * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
  38355. * to switch to captioning or Text.
  38356. */
  38357. // With that in mind, we ignore any data between an XDS control code and a
  38358. // subsequent closed-captioning control code.
  38359. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  38360. // NOTE: packet.type is the CEA608 field
  38361. if (this.setsTextOrXDSActive(packet)) {
  38362. this.activeCea608Channel_[packet.type] = null;
  38363. } else if (this.setsChannel1Active(packet)) {
  38364. this.activeCea608Channel_[packet.type] = 0;
  38365. } else if (this.setsChannel2Active(packet)) {
  38366. this.activeCea608Channel_[packet.type] = 1;
  38367. }
  38368. if (this.activeCea608Channel_[packet.type] === null) {
  38369. // If we haven't received anything to set the active channel, or the
  38370. // packets are Text/XDS data, discard the data; we don't want jumbled
  38371. // captions
  38372. return;
  38373. }
  38374. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  38375. };
  38376. CaptionStream.prototype.setsChannel1Active = function (packet) {
  38377. return (packet.ccData & 0x7800) === 0x1000;
  38378. };
  38379. CaptionStream.prototype.setsChannel2Active = function (packet) {
  38380. return (packet.ccData & 0x7800) === 0x1800;
  38381. };
  38382. CaptionStream.prototype.setsTextOrXDSActive = function (packet) {
  38383. return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
  38384. }; // ----------------------
  38385. // Session to Application
  38386. // ----------------------
  38387. // This hash maps non-ASCII, special, and extended character codes to their
  38388. // proper Unicode equivalent. The first keys that are only a single byte
  38389. // are the non-standard ASCII characters, which simply map the CEA608 byte
  38390. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  38391. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  38392. // can be performed regardless of the field and data channel on which the
  38393. // character code was received.
  38394. var CHARACTER_TRANSLATION = {
  38395. 0x2a: 0xe1,
  38396. // á
  38397. 0x5c: 0xe9,
  38398. // é
  38399. 0x5e: 0xed,
  38400. // í
  38401. 0x5f: 0xf3,
  38402. // ó
  38403. 0x60: 0xfa,
  38404. // ú
  38405. 0x7b: 0xe7,
  38406. // ç
  38407. 0x7c: 0xf7,
  38408. // ÷
  38409. 0x7d: 0xd1,
  38410. // Ñ
  38411. 0x7e: 0xf1,
  38412. // ñ
  38413. 0x7f: 0x2588,
  38414. // █
  38415. 0x0130: 0xae,
  38416. // ®
  38417. 0x0131: 0xb0,
  38418. // °
  38419. 0x0132: 0xbd,
  38420. // ½
  38421. 0x0133: 0xbf,
  38422. // ¿
  38423. 0x0134: 0x2122,
  38424. // ™
  38425. 0x0135: 0xa2,
  38426. // ¢
  38427. 0x0136: 0xa3,
  38428. // £
  38429. 0x0137: 0x266a,
  38430. // ♪
  38431. 0x0138: 0xe0,
  38432. // à
  38433. 0x0139: 0xa0,
  38434. //
  38435. 0x013a: 0xe8,
  38436. // è
  38437. 0x013b: 0xe2,
  38438. // â
  38439. 0x013c: 0xea,
  38440. // ê
  38441. 0x013d: 0xee,
  38442. // î
  38443. 0x013e: 0xf4,
  38444. // ô
  38445. 0x013f: 0xfb,
  38446. // û
  38447. 0x0220: 0xc1,
  38448. // Á
  38449. 0x0221: 0xc9,
  38450. // É
  38451. 0x0222: 0xd3,
  38452. // Ó
  38453. 0x0223: 0xda,
  38454. // Ú
  38455. 0x0224: 0xdc,
  38456. // Ü
  38457. 0x0225: 0xfc,
  38458. // ü
  38459. 0x0226: 0x2018,
  38460. // ‘
  38461. 0x0227: 0xa1,
  38462. // ¡
  38463. 0x0228: 0x2a,
  38464. // *
  38465. 0x0229: 0x27,
  38466. // '
  38467. 0x022a: 0x2014,
  38468. // —
  38469. 0x022b: 0xa9,
  38470. // ©
  38471. 0x022c: 0x2120,
  38472. // ℠
  38473. 0x022d: 0x2022,
  38474. // •
  38475. 0x022e: 0x201c,
  38476. // “
  38477. 0x022f: 0x201d,
  38478. // ”
  38479. 0x0230: 0xc0,
  38480. // À
  38481. 0x0231: 0xc2,
  38482. // Â
  38483. 0x0232: 0xc7,
  38484. // Ç
  38485. 0x0233: 0xc8,
  38486. // È
  38487. 0x0234: 0xca,
  38488. // Ê
  38489. 0x0235: 0xcb,
  38490. // Ë
  38491. 0x0236: 0xeb,
  38492. // ë
  38493. 0x0237: 0xce,
  38494. // Î
  38495. 0x0238: 0xcf,
  38496. // Ï
  38497. 0x0239: 0xef,
  38498. // ï
  38499. 0x023a: 0xd4,
  38500. // Ô
  38501. 0x023b: 0xd9,
  38502. // Ù
  38503. 0x023c: 0xf9,
  38504. // ù
  38505. 0x023d: 0xdb,
  38506. // Û
  38507. 0x023e: 0xab,
  38508. // «
  38509. 0x023f: 0xbb,
  38510. // »
  38511. 0x0320: 0xc3,
  38512. // Ã
  38513. 0x0321: 0xe3,
  38514. // ã
  38515. 0x0322: 0xcd,
  38516. // Í
  38517. 0x0323: 0xcc,
  38518. // Ì
  38519. 0x0324: 0xec,
  38520. // ì
  38521. 0x0325: 0xd2,
  38522. // Ò
  38523. 0x0326: 0xf2,
  38524. // ò
  38525. 0x0327: 0xd5,
  38526. // Õ
  38527. 0x0328: 0xf5,
  38528. // õ
  38529. 0x0329: 0x7b,
  38530. // {
  38531. 0x032a: 0x7d,
  38532. // }
  38533. 0x032b: 0x5c,
  38534. // \
  38535. 0x032c: 0x5e,
  38536. // ^
  38537. 0x032d: 0x5f,
  38538. // _
  38539. 0x032e: 0x7c,
  38540. // |
  38541. 0x032f: 0x7e,
  38542. // ~
  38543. 0x0330: 0xc4,
  38544. // Ä
  38545. 0x0331: 0xe4,
  38546. // ä
  38547. 0x0332: 0xd6,
  38548. // Ö
  38549. 0x0333: 0xf6,
  38550. // ö
  38551. 0x0334: 0xdf,
  38552. // ß
  38553. 0x0335: 0xa5,
  38554. // ¥
  38555. 0x0336: 0xa4,
  38556. // ¤
  38557. 0x0337: 0x2502,
  38558. // │
  38559. 0x0338: 0xc5,
  38560. // Å
  38561. 0x0339: 0xe5,
  38562. // å
  38563. 0x033a: 0xd8,
  38564. // Ø
  38565. 0x033b: 0xf8,
  38566. // ø
  38567. 0x033c: 0x250c,
  38568. // ┌
  38569. 0x033d: 0x2510,
  38570. // ┐
  38571. 0x033e: 0x2514,
  38572. // └
  38573. 0x033f: 0x2518 // ┘
  38574. };
  38575. var getCharFromCode = function getCharFromCode(code) {
  38576. if (code === null) {
  38577. return '';
  38578. }
  38579. code = CHARACTER_TRANSLATION[code] || code;
  38580. return String.fromCharCode(code);
  38581. }; // the index of the last row in a CEA-608 display buffer
  38582. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  38583. // getting it through bit logic.
  38584. 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
  38585. // cells. The "bottom" row is the last element in the outer array.
  38586. var createDisplayBuffer = function createDisplayBuffer() {
  38587. var result = [],
  38588. i = BOTTOM_ROW + 1;
  38589. while (i--) {
  38590. result.push('');
  38591. }
  38592. return result;
  38593. };
  38594. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  38595. Cea608Stream.prototype.init.call(this);
  38596. this.field_ = field || 0;
  38597. this.dataChannel_ = dataChannel || 0;
  38598. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  38599. this.setConstants();
  38600. this.reset();
  38601. this.push = function (packet) {
  38602. var data, swap, char0, char1, text; // remove the parity bits
  38603. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  38604. if (data === this.lastControlCode_) {
  38605. this.lastControlCode_ = null;
  38606. return;
  38607. } // Store control codes
  38608. if ((data & 0xf000) === 0x1000) {
  38609. this.lastControlCode_ = data;
  38610. } else if (data !== this.PADDING_) {
  38611. this.lastControlCode_ = null;
  38612. }
  38613. char0 = data >>> 8;
  38614. char1 = data & 0xff;
  38615. if (data === this.PADDING_) {
  38616. return;
  38617. } else if (data === this.RESUME_CAPTION_LOADING_) {
  38618. this.mode_ = 'popOn';
  38619. } else if (data === this.END_OF_CAPTION_) {
  38620. // If an EOC is received while in paint-on mode, the displayed caption
  38621. // text should be swapped to non-displayed memory as if it was a pop-on
  38622. // caption. Because of that, we should explicitly switch back to pop-on
  38623. // mode
  38624. this.mode_ = 'popOn';
  38625. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  38626. this.flushDisplayed(packet.pts); // flip memory
  38627. swap = this.displayed_;
  38628. this.displayed_ = this.nonDisplayed_;
  38629. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  38630. this.startPts_ = packet.pts;
  38631. } else if (data === this.ROLL_UP_2_ROWS_) {
  38632. this.rollUpRows_ = 2;
  38633. this.setRollUp(packet.pts);
  38634. } else if (data === this.ROLL_UP_3_ROWS_) {
  38635. this.rollUpRows_ = 3;
  38636. this.setRollUp(packet.pts);
  38637. } else if (data === this.ROLL_UP_4_ROWS_) {
  38638. this.rollUpRows_ = 4;
  38639. this.setRollUp(packet.pts);
  38640. } else if (data === this.CARRIAGE_RETURN_) {
  38641. this.clearFormatting(packet.pts);
  38642. this.flushDisplayed(packet.pts);
  38643. this.shiftRowsUp_();
  38644. this.startPts_ = packet.pts;
  38645. } else if (data === this.BACKSPACE_) {
  38646. if (this.mode_ === 'popOn') {
  38647. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  38648. } else {
  38649. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  38650. }
  38651. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  38652. this.flushDisplayed(packet.pts);
  38653. this.displayed_ = createDisplayBuffer();
  38654. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  38655. this.nonDisplayed_ = createDisplayBuffer();
  38656. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  38657. if (this.mode_ !== 'paintOn') {
  38658. // NOTE: This should be removed when proper caption positioning is
  38659. // implemented
  38660. this.flushDisplayed(packet.pts);
  38661. this.displayed_ = createDisplayBuffer();
  38662. }
  38663. this.mode_ = 'paintOn';
  38664. this.startPts_ = packet.pts; // Append special characters to caption text
  38665. } else if (this.isSpecialCharacter(char0, char1)) {
  38666. // Bitmask char0 so that we can apply character transformations
  38667. // regardless of field and data channel.
  38668. // Then byte-shift to the left and OR with char1 so we can pass the
  38669. // entire character code to `getCharFromCode`.
  38670. char0 = (char0 & 0x03) << 8;
  38671. text = getCharFromCode(char0 | char1);
  38672. this[this.mode_](packet.pts, text);
  38673. this.column_++; // Append extended characters to caption text
  38674. } else if (this.isExtCharacter(char0, char1)) {
  38675. // Extended characters always follow their "non-extended" equivalents.
  38676. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  38677. // decoders are supposed to drop the "è", while compliant decoders
  38678. // backspace the "e" and insert "è".
  38679. // Delete the previous character
  38680. if (this.mode_ === 'popOn') {
  38681. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  38682. } else {
  38683. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  38684. } // Bitmask char0 so that we can apply character transformations
  38685. // regardless of field and data channel.
  38686. // Then byte-shift to the left and OR with char1 so we can pass the
  38687. // entire character code to `getCharFromCode`.
  38688. char0 = (char0 & 0x03) << 8;
  38689. text = getCharFromCode(char0 | char1);
  38690. this[this.mode_](packet.pts, text);
  38691. this.column_++; // Process mid-row codes
  38692. } else if (this.isMidRowCode(char0, char1)) {
  38693. // Attributes are not additive, so clear all formatting
  38694. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  38695. // should be replaced with spaces, so add one now
  38696. this[this.mode_](packet.pts, ' ');
  38697. this.column_++;
  38698. if ((char1 & 0xe) === 0xe) {
  38699. this.addFormatting(packet.pts, ['i']);
  38700. }
  38701. if ((char1 & 0x1) === 0x1) {
  38702. this.addFormatting(packet.pts, ['u']);
  38703. } // Detect offset control codes and adjust cursor
  38704. } else if (this.isOffsetControlCode(char0, char1)) {
  38705. // Cursor position is set by indent PAC (see below) in 4-column
  38706. // increments, with an additional offset code of 1-3 to reach any
  38707. // of the 32 columns specified by CEA-608. So all we need to do
  38708. // here is increment the column cursor by the given offset.
  38709. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  38710. } else if (this.isPAC(char0, char1)) {
  38711. // There's no logic for PAC -> row mapping, so we have to just
  38712. // find the row code in an array and use its index :(
  38713. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  38714. if (this.mode_ === 'rollUp') {
  38715. // This implies that the base row is incorrectly set.
  38716. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  38717. // of roll-up rows set.
  38718. if (row - this.rollUpRows_ + 1 < 0) {
  38719. row = this.rollUpRows_ - 1;
  38720. }
  38721. this.setRollUp(packet.pts, row);
  38722. }
  38723. if (row !== this.row_) {
  38724. // formatting is only persistent for current row
  38725. this.clearFormatting(packet.pts);
  38726. this.row_ = row;
  38727. } // All PACs can apply underline, so detect and apply
  38728. // (All odd-numbered second bytes set underline)
  38729. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  38730. this.addFormatting(packet.pts, ['u']);
  38731. }
  38732. if ((data & 0x10) === 0x10) {
  38733. // We've got an indent level code. Each successive even number
  38734. // increments the column cursor by 4, so we can get the desired
  38735. // column position by bit-shifting to the right (to get n/2)
  38736. // and multiplying by 4.
  38737. this.column_ = ((data & 0xe) >> 1) * 4;
  38738. }
  38739. if (this.isColorPAC(char1)) {
  38740. // it's a color code, though we only support white, which
  38741. // can be either normal or italicized. white italics can be
  38742. // either 0x4e or 0x6e depending on the row, so we just
  38743. // bitwise-and with 0xe to see if italics should be turned on
  38744. if ((char1 & 0xe) === 0xe) {
  38745. this.addFormatting(packet.pts, ['i']);
  38746. }
  38747. } // We have a normal character in char0, and possibly one in char1
  38748. } else if (this.isNormalChar(char0)) {
  38749. if (char1 === 0x00) {
  38750. char1 = null;
  38751. }
  38752. text = getCharFromCode(char0);
  38753. text += getCharFromCode(char1);
  38754. this[this.mode_](packet.pts, text);
  38755. this.column_ += text.length;
  38756. } // finish data processing
  38757. };
  38758. };
  38759. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  38760. // display buffer
  38761. Cea608Stream.prototype.flushDisplayed = function (pts) {
  38762. var content = this.displayed_ // remove spaces from the start and end of the string
  38763. .map(function (row) {
  38764. try {
  38765. return row.trim();
  38766. } catch (e) {
  38767. // Ordinarily, this shouldn't happen. However, caption
  38768. // parsing errors should not throw exceptions and
  38769. // break playback.
  38770. // eslint-disable-next-line no-console
  38771. console.error('Skipping malformed caption.');
  38772. return '';
  38773. }
  38774. }) // combine all text rows to display in one cue
  38775. .join('\n') // and remove blank rows from the start and end, but not the middle
  38776. .replace(/^\n+|\n+$/g, '');
  38777. if (content.length) {
  38778. this.trigger('data', {
  38779. startPts: this.startPts_,
  38780. endPts: pts,
  38781. text: content,
  38782. stream: this.name_
  38783. });
  38784. }
  38785. };
  38786. /**
  38787. * Zero out the data, used for startup and on seek
  38788. */
  38789. Cea608Stream.prototype.reset = function () {
  38790. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  38791. // actually display captions. If a caption is shifted to a row
  38792. // with a lower index than this, it is cleared from the display
  38793. // buffer
  38794. this.topRow_ = 0;
  38795. this.startPts_ = 0;
  38796. this.displayed_ = createDisplayBuffer();
  38797. this.nonDisplayed_ = createDisplayBuffer();
  38798. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  38799. this.column_ = 0;
  38800. this.row_ = BOTTOM_ROW;
  38801. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  38802. this.formatting_ = [];
  38803. };
  38804. /**
  38805. * Sets up control code and related constants for this instance
  38806. */
  38807. Cea608Stream.prototype.setConstants = function () {
  38808. // The following attributes have these uses:
  38809. // ext_ : char0 for mid-row codes, and the base for extended
  38810. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  38811. // extended codes)
  38812. // control_: char0 for control codes, except byte-shifted to the
  38813. // left so that we can do this.control_ | CONTROL_CODE
  38814. // offset_: char0 for tab offset codes
  38815. //
  38816. // It's also worth noting that control codes, and _only_ control codes,
  38817. // differ between field 1 and field2. Field 2 control codes are always
  38818. // their field 1 value plus 1. That's why there's the "| field" on the
  38819. // control value.
  38820. if (this.dataChannel_ === 0) {
  38821. this.BASE_ = 0x10;
  38822. this.EXT_ = 0x11;
  38823. this.CONTROL_ = (0x14 | this.field_) << 8;
  38824. this.OFFSET_ = 0x17;
  38825. } else if (this.dataChannel_ === 1) {
  38826. this.BASE_ = 0x18;
  38827. this.EXT_ = 0x19;
  38828. this.CONTROL_ = (0x1c | this.field_) << 8;
  38829. this.OFFSET_ = 0x1f;
  38830. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  38831. // list is not exhaustive. For a more comprehensive listing and semantics see
  38832. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  38833. // Padding
  38834. this.PADDING_ = 0x0000; // Pop-on Mode
  38835. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  38836. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  38837. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  38838. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  38839. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  38840. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  38841. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  38842. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  38843. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  38844. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  38845. };
  38846. /**
  38847. * Detects if the 2-byte packet data is a special character
  38848. *
  38849. * Special characters have a second byte in the range 0x30 to 0x3f,
  38850. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  38851. * data channel 2).
  38852. *
  38853. * @param {Integer} char0 The first byte
  38854. * @param {Integer} char1 The second byte
  38855. * @return {Boolean} Whether the 2 bytes are an special character
  38856. */
  38857. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  38858. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  38859. };
  38860. /**
  38861. * Detects if the 2-byte packet data is an extended character
  38862. *
  38863. * Extended characters have a second byte in the range 0x20 to 0x3f,
  38864. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  38865. * 0x1a or 0x1b (for data channel 2).
  38866. *
  38867. * @param {Integer} char0 The first byte
  38868. * @param {Integer} char1 The second byte
  38869. * @return {Boolean} Whether the 2 bytes are an extended character
  38870. */
  38871. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  38872. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  38873. };
  38874. /**
  38875. * Detects if the 2-byte packet is a mid-row code
  38876. *
  38877. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  38878. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  38879. * channel 2).
  38880. *
  38881. * @param {Integer} char0 The first byte
  38882. * @param {Integer} char1 The second byte
  38883. * @return {Boolean} Whether the 2 bytes are a mid-row code
  38884. */
  38885. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  38886. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  38887. };
  38888. /**
  38889. * Detects if the 2-byte packet is an offset control code
  38890. *
  38891. * Offset control codes have a second byte in the range 0x21 to 0x23,
  38892. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  38893. * data channel 2).
  38894. *
  38895. * @param {Integer} char0 The first byte
  38896. * @param {Integer} char1 The second byte
  38897. * @return {Boolean} Whether the 2 bytes are an offset control code
  38898. */
  38899. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  38900. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  38901. };
  38902. /**
  38903. * Detects if the 2-byte packet is a Preamble Address Code
  38904. *
  38905. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  38906. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  38907. * range 0x40 to 0x7f.
  38908. *
  38909. * @param {Integer} char0 The first byte
  38910. * @param {Integer} char1 The second byte
  38911. * @return {Boolean} Whether the 2 bytes are a PAC
  38912. */
  38913. Cea608Stream.prototype.isPAC = function (char0, char1) {
  38914. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  38915. };
  38916. /**
  38917. * Detects if a packet's second byte is in the range of a PAC color code
  38918. *
  38919. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  38920. * 0x60 to 0x6f.
  38921. *
  38922. * @param {Integer} char1 The second byte
  38923. * @return {Boolean} Whether the byte is a color PAC
  38924. */
  38925. Cea608Stream.prototype.isColorPAC = function (char1) {
  38926. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  38927. };
  38928. /**
  38929. * Detects if a single byte is in the range of a normal character
  38930. *
  38931. * Normal text bytes are in the range 0x20 to 0x7f.
  38932. *
  38933. * @param {Integer} char The byte
  38934. * @return {Boolean} Whether the byte is a normal character
  38935. */
  38936. Cea608Stream.prototype.isNormalChar = function (_char) {
  38937. return _char >= 0x20 && _char <= 0x7f;
  38938. };
  38939. /**
  38940. * Configures roll-up
  38941. *
  38942. * @param {Integer} pts Current PTS
  38943. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  38944. * a new position
  38945. */
  38946. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  38947. // Reset the base row to the bottom row when switching modes
  38948. if (this.mode_ !== 'rollUp') {
  38949. this.row_ = BOTTOM_ROW;
  38950. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  38951. this.flushDisplayed(pts);
  38952. this.nonDisplayed_ = createDisplayBuffer();
  38953. this.displayed_ = createDisplayBuffer();
  38954. }
  38955. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  38956. // move currently displayed captions (up or down) to the new base row
  38957. for (var i = 0; i < this.rollUpRows_; i++) {
  38958. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  38959. this.displayed_[this.row_ - i] = '';
  38960. }
  38961. }
  38962. if (newBaseRow === undefined) {
  38963. newBaseRow = this.row_;
  38964. }
  38965. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  38966. }; // Adds the opening HTML tag for the passed character to the caption text,
  38967. // and keeps track of it for later closing
  38968. Cea608Stream.prototype.addFormatting = function (pts, format) {
  38969. this.formatting_ = this.formatting_.concat(format);
  38970. var text = format.reduce(function (text, format) {
  38971. return text + '<' + format + '>';
  38972. }, '');
  38973. this[this.mode_](pts, text);
  38974. }; // Adds HTML closing tags for current formatting to caption text and
  38975. // clears remembered formatting
  38976. Cea608Stream.prototype.clearFormatting = function (pts) {
  38977. if (!this.formatting_.length) {
  38978. return;
  38979. }
  38980. var text = this.formatting_.reverse().reduce(function (text, format) {
  38981. return text + '</' + format + '>';
  38982. }, '');
  38983. this.formatting_ = [];
  38984. this[this.mode_](pts, text);
  38985. }; // Mode Implementations
  38986. Cea608Stream.prototype.popOn = function (pts, text) {
  38987. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  38988. baseRow += text;
  38989. this.nonDisplayed_[this.row_] = baseRow;
  38990. };
  38991. Cea608Stream.prototype.rollUp = function (pts, text) {
  38992. var baseRow = this.displayed_[this.row_];
  38993. baseRow += text;
  38994. this.displayed_[this.row_] = baseRow;
  38995. };
  38996. Cea608Stream.prototype.shiftRowsUp_ = function () {
  38997. var i; // clear out inactive rows
  38998. for (i = 0; i < this.topRow_; i++) {
  38999. this.displayed_[i] = '';
  39000. }
  39001. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  39002. this.displayed_[i] = '';
  39003. } // shift displayed rows up
  39004. for (i = this.topRow_; i < this.row_; i++) {
  39005. this.displayed_[i] = this.displayed_[i + 1];
  39006. } // clear out the bottom row
  39007. this.displayed_[this.row_] = '';
  39008. };
  39009. Cea608Stream.prototype.paintOn = function (pts, text) {
  39010. var baseRow = this.displayed_[this.row_];
  39011. baseRow += text;
  39012. this.displayed_[this.row_] = baseRow;
  39013. }; // exports
  39014. var captionStream = {
  39015. CaptionStream: CaptionStream,
  39016. Cea608Stream: Cea608Stream
  39017. };
  39018. /**
  39019. * mux.js
  39020. *
  39021. * Copyright (c) Brightcove
  39022. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  39023. */
  39024. var streamTypes = {
  39025. H264_STREAM_TYPE: 0x1B,
  39026. ADTS_STREAM_TYPE: 0x0F,
  39027. METADATA_STREAM_TYPE: 0x15
  39028. };
  39029. var MAX_TS = 8589934592;
  39030. var RO_THRESH = 4294967296;
  39031. var handleRollover = function handleRollover(value, reference) {
  39032. var direction = 1;
  39033. if (value > reference) {
  39034. // If the current timestamp value is greater than our reference timestamp and we detect a
  39035. // timestamp rollover, this means the roll over is happening in the opposite direction.
  39036. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  39037. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  39038. // rollover point. In loading this segment, the timestamp values will be very large,
  39039. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  39040. // the time stamp to be `value - 2^33`.
  39041. direction = -1;
  39042. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  39043. // cause an incorrect adjustment.
  39044. while (Math.abs(reference - value) > RO_THRESH) {
  39045. value += direction * MAX_TS;
  39046. }
  39047. return value;
  39048. };
  39049. var TimestampRolloverStream = function TimestampRolloverStream(type) {
  39050. var lastDTS, referenceDTS;
  39051. TimestampRolloverStream.prototype.init.call(this);
  39052. this.type_ = type;
  39053. this.push = function (data) {
  39054. if (data.type !== this.type_) {
  39055. return;
  39056. }
  39057. if (referenceDTS === undefined) {
  39058. referenceDTS = data.dts;
  39059. }
  39060. data.dts = handleRollover(data.dts, referenceDTS);
  39061. data.pts = handleRollover(data.pts, referenceDTS);
  39062. lastDTS = data.dts;
  39063. this.trigger('data', data);
  39064. };
  39065. this.flush = function () {
  39066. referenceDTS = lastDTS;
  39067. this.trigger('done');
  39068. };
  39069. this.discontinuity = function () {
  39070. referenceDTS = void 0;
  39071. lastDTS = void 0;
  39072. };
  39073. };
  39074. TimestampRolloverStream.prototype = new stream();
  39075. var timestampRolloverStream = {
  39076. TimestampRolloverStream: TimestampRolloverStream,
  39077. handleRollover: handleRollover
  39078. };
  39079. var percentEncode = function percentEncode(bytes, start, end) {
  39080. var i,
  39081. result = '';
  39082. for (i = start; i < end; i++) {
  39083. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  39084. }
  39085. return result;
  39086. },
  39087. // return the string representation of the specified byte range,
  39088. // interpreted as UTf-8.
  39089. parseUtf8 = function parseUtf8(bytes, start, end) {
  39090. return decodeURIComponent(percentEncode(bytes, start, end));
  39091. },
  39092. // return the string representation of the specified byte range,
  39093. // interpreted as ISO-8859-1.
  39094. parseIso88591 = function parseIso88591(bytes, start, end) {
  39095. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  39096. },
  39097. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  39098. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  39099. },
  39100. tagParsers = {
  39101. TXXX: function TXXX(tag) {
  39102. var i;
  39103. if (tag.data[0] !== 3) {
  39104. // ignore frames with unrecognized character encodings
  39105. return;
  39106. }
  39107. for (i = 1; i < tag.data.length; i++) {
  39108. if (tag.data[i] === 0) {
  39109. // parse the text fields
  39110. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  39111. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  39112. break;
  39113. }
  39114. }
  39115. tag.data = tag.value;
  39116. },
  39117. WXXX: function WXXX(tag) {
  39118. var i;
  39119. if (tag.data[0] !== 3) {
  39120. // ignore frames with unrecognized character encodings
  39121. return;
  39122. }
  39123. for (i = 1; i < tag.data.length; i++) {
  39124. if (tag.data[i] === 0) {
  39125. // parse the description and URL fields
  39126. tag.description = parseUtf8(tag.data, 1, i);
  39127. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  39128. break;
  39129. }
  39130. }
  39131. },
  39132. PRIV: function PRIV(tag) {
  39133. var i;
  39134. for (i = 0; i < tag.data.length; i++) {
  39135. if (tag.data[i] === 0) {
  39136. // parse the description and URL fields
  39137. tag.owner = parseIso88591(tag.data, 0, i);
  39138. break;
  39139. }
  39140. }
  39141. tag.privateData = tag.data.subarray(i + 1);
  39142. tag.data = tag.privateData;
  39143. }
  39144. },
  39145. _MetadataStream;
  39146. _MetadataStream = function MetadataStream(options) {
  39147. var settings = {
  39148. debug: !!(options && options.debug),
  39149. // the bytes of the program-level descriptor field in MP2T
  39150. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  39151. // program element descriptors"
  39152. descriptor: options && options.descriptor
  39153. },
  39154. // the total size in bytes of the ID3 tag being parsed
  39155. tagSize = 0,
  39156. // tag data that is not complete enough to be parsed
  39157. buffer = [],
  39158. // the total number of bytes currently in the buffer
  39159. bufferSize = 0,
  39160. i;
  39161. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  39162. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  39163. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  39164. if (settings.descriptor) {
  39165. for (i = 0; i < settings.descriptor.length; i++) {
  39166. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  39167. }
  39168. }
  39169. this.push = function (chunk) {
  39170. var tag, frameStart, frameSize, frame, i, frameHeader;
  39171. if (chunk.type !== 'timed-metadata') {
  39172. return;
  39173. } // if data_alignment_indicator is set in the PES header,
  39174. // we must have the start of a new ID3 tag. Assume anything
  39175. // remaining in the buffer was malformed and throw it out
  39176. if (chunk.dataAlignmentIndicator) {
  39177. bufferSize = 0;
  39178. buffer.length = 0;
  39179. } // ignore events that don't look like ID3 data
  39180. 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))) {
  39181. if (settings.debug) {
  39182. // eslint-disable-next-line no-console
  39183. console.log('Skipping unrecognized metadata packet');
  39184. }
  39185. return;
  39186. } // add this chunk to the data we've collected so far
  39187. buffer.push(chunk);
  39188. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  39189. if (buffer.length === 1) {
  39190. // the frame size is transmitted as a 28-bit integer in the
  39191. // last four bytes of the ID3 header.
  39192. // The most significant bit of each byte is dropped and the
  39193. // results concatenated to recover the actual value.
  39194. tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  39195. // convenient for our comparisons to include it
  39196. tagSize += 10;
  39197. } // if the entire frame has not arrived, wait for more data
  39198. if (bufferSize < tagSize) {
  39199. return;
  39200. } // collect the entire frame so it can be parsed
  39201. tag = {
  39202. data: new Uint8Array(tagSize),
  39203. frames: [],
  39204. pts: buffer[0].pts,
  39205. dts: buffer[0].dts
  39206. };
  39207. for (i = 0; i < tagSize;) {
  39208. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  39209. i += buffer[0].data.byteLength;
  39210. bufferSize -= buffer[0].data.byteLength;
  39211. buffer.shift();
  39212. } // find the start of the first frame and the end of the tag
  39213. frameStart = 10;
  39214. if (tag.data[5] & 0x40) {
  39215. // advance the frame start past the extended header
  39216. frameStart += 4; // header size field
  39217. frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  39218. tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
  39219. } // parse one or more ID3 frames
  39220. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  39221. do {
  39222. // determine the number of bytes in this frame
  39223. frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  39224. if (frameSize < 1) {
  39225. // eslint-disable-next-line no-console
  39226. return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
  39227. }
  39228. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  39229. frame = {
  39230. id: frameHeader,
  39231. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  39232. };
  39233. frame.key = frame.id;
  39234. if (tagParsers[frame.id]) {
  39235. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  39236. // time for raw AAC data
  39237. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  39238. var d = frame.data,
  39239. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  39240. size *= 4;
  39241. size += d[7] & 0x03;
  39242. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  39243. // on the value of this frame
  39244. // we couldn't have known the appropriate pts and dts before
  39245. // parsing this ID3 tag so set those values now
  39246. if (tag.pts === undefined && tag.dts === undefined) {
  39247. tag.pts = frame.timeStamp;
  39248. tag.dts = frame.timeStamp;
  39249. }
  39250. this.trigger('timestamp', frame);
  39251. }
  39252. }
  39253. tag.frames.push(frame);
  39254. frameStart += 10; // advance past the frame header
  39255. frameStart += frameSize; // advance past the frame body
  39256. } while (frameStart < tagSize);
  39257. this.trigger('data', tag);
  39258. };
  39259. };
  39260. _MetadataStream.prototype = new stream();
  39261. var metadataStream = _MetadataStream;
  39262. var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
  39263. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  39264. var MP2T_PACKET_LENGTH = 188,
  39265. // bytes
  39266. SYNC_BYTE = 0x47;
  39267. /**
  39268. * Splits an incoming stream of binary data into MPEG-2 Transport
  39269. * Stream packets.
  39270. */
  39271. _TransportPacketStream = function TransportPacketStream() {
  39272. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  39273. bytesInBuffer = 0;
  39274. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  39275. /**
  39276. * Split a stream of data into M2TS packets
  39277. **/
  39278. this.push = function (bytes) {
  39279. var startIndex = 0,
  39280. endIndex = MP2T_PACKET_LENGTH,
  39281. everything; // If there are bytes remaining from the last segment, prepend them to the
  39282. // bytes that were pushed in
  39283. if (bytesInBuffer) {
  39284. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  39285. everything.set(buffer.subarray(0, bytesInBuffer));
  39286. everything.set(bytes, bytesInBuffer);
  39287. bytesInBuffer = 0;
  39288. } else {
  39289. everything = bytes;
  39290. } // While we have enough data for a packet
  39291. while (endIndex < everything.byteLength) {
  39292. // Look for a pair of start and end sync bytes in the data..
  39293. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  39294. // We found a packet so emit it and jump one whole packet forward in
  39295. // the stream
  39296. this.trigger('data', everything.subarray(startIndex, endIndex));
  39297. startIndex += MP2T_PACKET_LENGTH;
  39298. endIndex += MP2T_PACKET_LENGTH;
  39299. continue;
  39300. } // If we get here, we have somehow become de-synchronized and we need to step
  39301. // forward one byte at a time until we find a pair of sync bytes that denote
  39302. // a packet
  39303. startIndex++;
  39304. endIndex++;
  39305. } // If there was some data left over at the end of the segment that couldn't
  39306. // possibly be a whole packet, keep it because it might be the start of a packet
  39307. // that continues in the next segment
  39308. if (startIndex < everything.byteLength) {
  39309. buffer.set(everything.subarray(startIndex), 0);
  39310. bytesInBuffer = everything.byteLength - startIndex;
  39311. }
  39312. };
  39313. /**
  39314. * Passes identified M2TS packets to the TransportParseStream to be parsed
  39315. **/
  39316. this.flush = function () {
  39317. // If the buffer contains a whole packet when we are being flushed, emit it
  39318. // and empty the buffer. Otherwise hold onto the data because it may be
  39319. // important for decoding the next segment
  39320. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  39321. this.trigger('data', buffer);
  39322. bytesInBuffer = 0;
  39323. }
  39324. this.trigger('done');
  39325. };
  39326. };
  39327. _TransportPacketStream.prototype = new stream();
  39328. /**
  39329. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  39330. * forms of the individual transport stream packets.
  39331. */
  39332. _TransportParseStream = function TransportParseStream() {
  39333. var parsePsi, parsePat, parsePmt, self;
  39334. _TransportParseStream.prototype.init.call(this);
  39335. self = this;
  39336. this.packetsWaitingForPmt = [];
  39337. this.programMapTable = undefined;
  39338. parsePsi = function parsePsi(payload, psi) {
  39339. var offset = 0; // PSI packets may be split into multiple sections and those
  39340. // sections may be split into multiple packets. If a PSI
  39341. // section starts in this packet, the payload_unit_start_indicator
  39342. // will be true and the first byte of the payload will indicate
  39343. // the offset from the current position to the start of the
  39344. // section.
  39345. if (psi.payloadUnitStartIndicator) {
  39346. offset += payload[offset] + 1;
  39347. }
  39348. if (psi.type === 'pat') {
  39349. parsePat(payload.subarray(offset), psi);
  39350. } else {
  39351. parsePmt(payload.subarray(offset), psi);
  39352. }
  39353. };
  39354. parsePat = function parsePat(payload, pat) {
  39355. pat.section_number = payload[7]; // eslint-disable-line camelcase
  39356. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  39357. // skip the PSI header and parse the first PMT entry
  39358. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  39359. pat.pmtPid = self.pmtPid;
  39360. };
  39361. /**
  39362. * Parse out the relevant fields of a Program Map Table (PMT).
  39363. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  39364. * packet. The first byte in this array should be the table_id
  39365. * field.
  39366. * @param pmt {object} the object that should be decorated with
  39367. * fields parsed from the PMT.
  39368. */
  39369. parsePmt = function parsePmt(payload, pmt) {
  39370. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  39371. // take effect. We don't believe this should ever be the case
  39372. // for HLS but we'll ignore "forward" PMT declarations if we see
  39373. // them. Future PMT declarations have the current_next_indicator
  39374. // set to zero.
  39375. if (!(payload[5] & 0x01)) {
  39376. return;
  39377. } // overwrite any existing program map table
  39378. self.programMapTable = {
  39379. video: null,
  39380. audio: null,
  39381. 'timed-metadata': {}
  39382. }; // the mapping table ends at the end of the current section
  39383. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  39384. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  39385. // long the program info descriptors are
  39386. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  39387. offset = 12 + programInfoLength;
  39388. while (offset < tableEnd) {
  39389. var streamType = payload[offset];
  39390. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  39391. // TODO: should this be done for metadata too? for now maintain behavior of
  39392. // multiple metadata streams
  39393. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  39394. self.programMapTable.video = pid;
  39395. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  39396. self.programMapTable.audio = pid;
  39397. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  39398. // map pid to stream type for metadata streams
  39399. self.programMapTable['timed-metadata'][pid] = streamType;
  39400. } // move to the next table entry
  39401. // skip past the elementary stream descriptors, if present
  39402. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  39403. } // record the map on the packet as well
  39404. pmt.programMapTable = self.programMapTable;
  39405. };
  39406. /**
  39407. * Deliver a new MP2T packet to the next stream in the pipeline.
  39408. */
  39409. this.push = function (packet) {
  39410. var result = {},
  39411. offset = 4;
  39412. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  39413. result.pid = packet[1] & 0x1f;
  39414. result.pid <<= 8;
  39415. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  39416. // fifth byte of the TS packet header. The adaptation field is
  39417. // used to add stuffing to PES packets that don't fill a complete
  39418. // TS packet, and to specify some forms of timing and control data
  39419. // that we do not currently use.
  39420. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  39421. offset += packet[offset] + 1;
  39422. } // parse the rest of the packet based on the type
  39423. if (result.pid === 0) {
  39424. result.type = 'pat';
  39425. parsePsi(packet.subarray(offset), result);
  39426. this.trigger('data', result);
  39427. } else if (result.pid === this.pmtPid) {
  39428. result.type = 'pmt';
  39429. parsePsi(packet.subarray(offset), result);
  39430. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  39431. while (this.packetsWaitingForPmt.length) {
  39432. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  39433. }
  39434. } else if (this.programMapTable === undefined) {
  39435. // When we have not seen a PMT yet, defer further processing of
  39436. // PES packets until one has been parsed
  39437. this.packetsWaitingForPmt.push([packet, offset, result]);
  39438. } else {
  39439. this.processPes_(packet, offset, result);
  39440. }
  39441. };
  39442. this.processPes_ = function (packet, offset, result) {
  39443. // set the appropriate stream type
  39444. if (result.pid === this.programMapTable.video) {
  39445. result.streamType = streamTypes.H264_STREAM_TYPE;
  39446. } else if (result.pid === this.programMapTable.audio) {
  39447. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  39448. } else {
  39449. // if not video or audio, it is timed-metadata or unknown
  39450. // if unknown, streamType will be undefined
  39451. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  39452. }
  39453. result.type = 'pes';
  39454. result.data = packet.subarray(offset);
  39455. this.trigger('data', result);
  39456. };
  39457. };
  39458. _TransportParseStream.prototype = new stream();
  39459. _TransportParseStream.STREAM_TYPES = {
  39460. h264: 0x1b,
  39461. adts: 0x0f
  39462. };
  39463. /**
  39464. * Reconsistutes program elementary stream (PES) packets from parsed
  39465. * transport stream packets. That is, if you pipe an
  39466. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  39467. * events will be events which capture the bytes for individual PES
  39468. * packets plus relevant metadata that has been extracted from the
  39469. * container.
  39470. */
  39471. _ElementaryStream = function ElementaryStream() {
  39472. var self = this,
  39473. // PES packet fragments
  39474. video = {
  39475. data: [],
  39476. size: 0
  39477. },
  39478. audio = {
  39479. data: [],
  39480. size: 0
  39481. },
  39482. timedMetadata = {
  39483. data: [],
  39484. size: 0
  39485. },
  39486. parsePes = function parsePes(payload, pes) {
  39487. var ptsDtsFlags; // get the packet length, this will be 0 for video
  39488. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  39489. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  39490. // and a DTS value. Determine what combination of values is
  39491. // available to work with.
  39492. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  39493. // performs all bitwise operations on 32-bit integers but javascript
  39494. // supports a much greater range (52-bits) of integer using standard
  39495. // mathematical operations.
  39496. // We construct a 31-bit value using bitwise operators over the 31
  39497. // most significant bits and then multiply by 4 (equal to a left-shift
  39498. // of 2) before we add the final 2 least significant bits of the
  39499. // timestamp (equal to an OR.)
  39500. if (ptsDtsFlags & 0xC0) {
  39501. // the PTS and DTS are not written out directly. For information
  39502. // on how they are encoded, see
  39503. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  39504. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  39505. pes.pts *= 4; // Left shift by 2
  39506. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  39507. pes.dts = pes.pts;
  39508. if (ptsDtsFlags & 0x40) {
  39509. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  39510. pes.dts *= 4; // Left shift by 2
  39511. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  39512. }
  39513. } // the data section starts immediately after the PES header.
  39514. // pes_header_data_length specifies the number of header bytes
  39515. // that follow the last byte of the field.
  39516. pes.data = payload.subarray(9 + payload[8]);
  39517. },
  39518. /**
  39519. * Pass completely parsed PES packets to the next stream in the pipeline
  39520. **/
  39521. flushStream = function flushStream(stream$$1, type, forceFlush) {
  39522. var packetData = new Uint8Array(stream$$1.size),
  39523. event = {
  39524. type: type
  39525. },
  39526. i = 0,
  39527. offset = 0,
  39528. packetFlushable = false,
  39529. fragment; // do nothing if there is not enough buffered data for a complete
  39530. // PES header
  39531. if (!stream$$1.data.length || stream$$1.size < 9) {
  39532. return;
  39533. }
  39534. event.trackId = stream$$1.data[0].pid; // reassemble the packet
  39535. for (i = 0; i < stream$$1.data.length; i++) {
  39536. fragment = stream$$1.data[i];
  39537. packetData.set(fragment.data, offset);
  39538. offset += fragment.data.byteLength;
  39539. } // parse assembled packet's PES header
  39540. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  39541. // check that there is enough stream data to fill the packet
  39542. packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
  39543. if (forceFlush || packetFlushable) {
  39544. stream$$1.size = 0;
  39545. stream$$1.data.length = 0;
  39546. } // only emit packets that are complete. this is to avoid assembling
  39547. // incomplete PES packets due to poor segmentation
  39548. if (packetFlushable) {
  39549. self.trigger('data', event);
  39550. }
  39551. };
  39552. _ElementaryStream.prototype.init.call(this);
  39553. /**
  39554. * Identifies M2TS packet types and parses PES packets using metadata
  39555. * parsed from the PMT
  39556. **/
  39557. this.push = function (data) {
  39558. ({
  39559. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  39560. // have any meaningful metadata
  39561. },
  39562. pes: function pes() {
  39563. var stream$$1, streamType;
  39564. switch (data.streamType) {
  39565. case streamTypes.H264_STREAM_TYPE:
  39566. case streamTypes.H264_STREAM_TYPE:
  39567. stream$$1 = video;
  39568. streamType = 'video';
  39569. break;
  39570. case streamTypes.ADTS_STREAM_TYPE:
  39571. stream$$1 = audio;
  39572. streamType = 'audio';
  39573. break;
  39574. case streamTypes.METADATA_STREAM_TYPE:
  39575. stream$$1 = timedMetadata;
  39576. streamType = 'timed-metadata';
  39577. break;
  39578. default:
  39579. // ignore unknown stream types
  39580. return;
  39581. } // if a new packet is starting, we can flush the completed
  39582. // packet
  39583. if (data.payloadUnitStartIndicator) {
  39584. flushStream(stream$$1, streamType, true);
  39585. } // buffer this fragment until we are sure we've received the
  39586. // complete payload
  39587. stream$$1.data.push(data);
  39588. stream$$1.size += data.data.byteLength;
  39589. },
  39590. pmt: function pmt() {
  39591. var event = {
  39592. type: 'metadata',
  39593. tracks: []
  39594. },
  39595. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  39596. if (programMapTable.video !== null) {
  39597. event.tracks.push({
  39598. timelineStartInfo: {
  39599. baseMediaDecodeTime: 0
  39600. },
  39601. id: +programMapTable.video,
  39602. codec: 'avc',
  39603. type: 'video'
  39604. });
  39605. }
  39606. if (programMapTable.audio !== null) {
  39607. event.tracks.push({
  39608. timelineStartInfo: {
  39609. baseMediaDecodeTime: 0
  39610. },
  39611. id: +programMapTable.audio,
  39612. codec: 'adts',
  39613. type: 'audio'
  39614. });
  39615. }
  39616. self.trigger('data', event);
  39617. }
  39618. })[data.type]();
  39619. };
  39620. /**
  39621. * Flush any remaining input. Video PES packets may be of variable
  39622. * length. Normally, the start of a new video packet can trigger the
  39623. * finalization of the previous packet. That is not possible if no
  39624. * more video is forthcoming, however. In that case, some other
  39625. * mechanism (like the end of the file) has to be employed. When it is
  39626. * clear that no additional data is forthcoming, calling this method
  39627. * will flush the buffered packets.
  39628. */
  39629. this.flush = function () {
  39630. // !!THIS ORDER IS IMPORTANT!!
  39631. // video first then audio
  39632. flushStream(video, 'video');
  39633. flushStream(audio, 'audio');
  39634. flushStream(timedMetadata, 'timed-metadata');
  39635. this.trigger('done');
  39636. };
  39637. };
  39638. _ElementaryStream.prototype = new stream();
  39639. var m2ts = {
  39640. PAT_PID: 0x0000,
  39641. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  39642. TransportPacketStream: _TransportPacketStream,
  39643. TransportParseStream: _TransportParseStream,
  39644. ElementaryStream: _ElementaryStream,
  39645. TimestampRolloverStream: TimestampRolloverStream$1,
  39646. CaptionStream: captionStream.CaptionStream,
  39647. Cea608Stream: captionStream.Cea608Stream,
  39648. MetadataStream: metadataStream
  39649. };
  39650. for (var type in streamTypes) {
  39651. if (streamTypes.hasOwnProperty(type)) {
  39652. m2ts[type] = streamTypes[type];
  39653. }
  39654. }
  39655. var m2ts_1 = m2ts;
  39656. var _AdtsStream;
  39657. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  39658. /*
  39659. * Accepts a ElementaryStream and emits data events with parsed
  39660. * AAC Audio Frames of the individual packets. Input audio in ADTS
  39661. * format is unpacked and re-emitted as AAC frames.
  39662. *
  39663. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  39664. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  39665. */
  39666. _AdtsStream = function AdtsStream() {
  39667. var buffer;
  39668. _AdtsStream.prototype.init.call(this);
  39669. this.push = function (packet) {
  39670. var i = 0,
  39671. frameNum = 0,
  39672. frameLength,
  39673. protectionSkipBytes,
  39674. frameEnd,
  39675. oldBuffer,
  39676. sampleCount,
  39677. adtsFrameDuration;
  39678. if (packet.type !== 'audio') {
  39679. // ignore non-audio data
  39680. return;
  39681. } // Prepend any data in the buffer to the input data so that we can parse
  39682. // aac frames the cross a PES packet boundary
  39683. if (buffer) {
  39684. oldBuffer = buffer;
  39685. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  39686. buffer.set(oldBuffer);
  39687. buffer.set(packet.data, oldBuffer.byteLength);
  39688. } else {
  39689. buffer = packet.data;
  39690. } // unpack any ADTS frames which have been fully received
  39691. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  39692. while (i + 5 < buffer.length) {
  39693. // Loook for the start of an ADTS header..
  39694. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  39695. // If a valid header was not found, jump one forward and attempt to
  39696. // find a valid ADTS header starting at the next byte
  39697. i++;
  39698. continue;
  39699. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  39700. // end of the ADTS header
  39701. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  39702. // end of the sync sequence
  39703. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  39704. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  39705. adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
  39706. frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
  39707. // and wait for more data
  39708. if (buffer.byteLength < frameEnd) {
  39709. return;
  39710. } // Otherwise, deliver the complete AAC frame
  39711. this.trigger('data', {
  39712. pts: packet.pts + frameNum * adtsFrameDuration,
  39713. dts: packet.dts + frameNum * adtsFrameDuration,
  39714. sampleCount: sampleCount,
  39715. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  39716. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  39717. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  39718. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  39719. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  39720. samplesize: 16,
  39721. data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
  39722. }); // If the buffer is empty, clear it and return
  39723. if (buffer.byteLength === frameEnd) {
  39724. buffer = undefined;
  39725. return;
  39726. }
  39727. frameNum++; // Remove the finished frame from the buffer and start the process again
  39728. buffer = buffer.subarray(frameEnd);
  39729. }
  39730. };
  39731. this.flush = function () {
  39732. this.trigger('done');
  39733. };
  39734. };
  39735. _AdtsStream.prototype = new stream();
  39736. var adts = _AdtsStream;
  39737. /**
  39738. * mux.js
  39739. *
  39740. * Copyright (c) Brightcove
  39741. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  39742. */
  39743. var ExpGolomb;
  39744. /**
  39745. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  39746. * scheme used by h264.
  39747. */
  39748. ExpGolomb = function ExpGolomb(workingData) {
  39749. var // the number of bytes left to examine in workingData
  39750. workingBytesAvailable = workingData.byteLength,
  39751. // the current word being examined
  39752. workingWord = 0,
  39753. // :uint
  39754. // the number of bits left to examine in the current word
  39755. workingBitsAvailable = 0; // :uint;
  39756. // ():uint
  39757. this.length = function () {
  39758. return 8 * workingBytesAvailable;
  39759. }; // ():uint
  39760. this.bitsAvailable = function () {
  39761. return 8 * workingBytesAvailable + workingBitsAvailable;
  39762. }; // ():void
  39763. this.loadWord = function () {
  39764. var position = workingData.byteLength - workingBytesAvailable,
  39765. workingBytes = new Uint8Array(4),
  39766. availableBytes = Math.min(4, workingBytesAvailable);
  39767. if (availableBytes === 0) {
  39768. throw new Error('no bytes available');
  39769. }
  39770. workingBytes.set(workingData.subarray(position, position + availableBytes));
  39771. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  39772. workingBitsAvailable = availableBytes * 8;
  39773. workingBytesAvailable -= availableBytes;
  39774. }; // (count:int):void
  39775. this.skipBits = function (count) {
  39776. var skipBytes; // :int
  39777. if (workingBitsAvailable > count) {
  39778. workingWord <<= count;
  39779. workingBitsAvailable -= count;
  39780. } else {
  39781. count -= workingBitsAvailable;
  39782. skipBytes = Math.floor(count / 8);
  39783. count -= skipBytes * 8;
  39784. workingBytesAvailable -= skipBytes;
  39785. this.loadWord();
  39786. workingWord <<= count;
  39787. workingBitsAvailable -= count;
  39788. }
  39789. }; // (size:int):uint
  39790. this.readBits = function (size) {
  39791. var bits = Math.min(workingBitsAvailable, size),
  39792. // :uint
  39793. valu = workingWord >>> 32 - bits; // :uint
  39794. // if size > 31, handle error
  39795. workingBitsAvailable -= bits;
  39796. if (workingBitsAvailable > 0) {
  39797. workingWord <<= bits;
  39798. } else if (workingBytesAvailable > 0) {
  39799. this.loadWord();
  39800. }
  39801. bits = size - bits;
  39802. if (bits > 0) {
  39803. return valu << bits | this.readBits(bits);
  39804. }
  39805. return valu;
  39806. }; // ():uint
  39807. this.skipLeadingZeros = function () {
  39808. var leadingZeroCount; // :uint
  39809. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  39810. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  39811. // the first bit of working word is 1
  39812. workingWord <<= leadingZeroCount;
  39813. workingBitsAvailable -= leadingZeroCount;
  39814. return leadingZeroCount;
  39815. }
  39816. } // we exhausted workingWord and still have not found a 1
  39817. this.loadWord();
  39818. return leadingZeroCount + this.skipLeadingZeros();
  39819. }; // ():void
  39820. this.skipUnsignedExpGolomb = function () {
  39821. this.skipBits(1 + this.skipLeadingZeros());
  39822. }; // ():void
  39823. this.skipExpGolomb = function () {
  39824. this.skipBits(1 + this.skipLeadingZeros());
  39825. }; // ():uint
  39826. this.readUnsignedExpGolomb = function () {
  39827. var clz = this.skipLeadingZeros(); // :uint
  39828. return this.readBits(clz + 1) - 1;
  39829. }; // ():int
  39830. this.readExpGolomb = function () {
  39831. var valu = this.readUnsignedExpGolomb(); // :int
  39832. if (0x01 & valu) {
  39833. // the number is odd if the low order bit is set
  39834. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  39835. }
  39836. return -1 * (valu >>> 1); // divide by two then make it negative
  39837. }; // Some convenience functions
  39838. // :Boolean
  39839. this.readBoolean = function () {
  39840. return this.readBits(1) === 1;
  39841. }; // ():int
  39842. this.readUnsignedByte = function () {
  39843. return this.readBits(8);
  39844. };
  39845. this.loadWord();
  39846. };
  39847. var expGolomb = ExpGolomb;
  39848. var _H264Stream, _NalByteStream;
  39849. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  39850. /**
  39851. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  39852. */
  39853. _NalByteStream = function NalByteStream() {
  39854. var syncPoint = 0,
  39855. i,
  39856. buffer;
  39857. _NalByteStream.prototype.init.call(this);
  39858. /*
  39859. * Scans a byte stream and triggers a data event with the NAL units found.
  39860. * @param {Object} data Event received from H264Stream
  39861. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  39862. *
  39863. * @see H264Stream.push
  39864. */
  39865. this.push = function (data) {
  39866. var swapBuffer;
  39867. if (!buffer) {
  39868. buffer = data.data;
  39869. } else {
  39870. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  39871. swapBuffer.set(buffer);
  39872. swapBuffer.set(data.data, buffer.byteLength);
  39873. buffer = swapBuffer;
  39874. } // Rec. ITU-T H.264, Annex B
  39875. // scan for NAL unit boundaries
  39876. // a match looks like this:
  39877. // 0 0 1 .. NAL .. 0 0 1
  39878. // ^ sync point ^ i
  39879. // or this:
  39880. // 0 0 1 .. NAL .. 0 0 0
  39881. // ^ sync point ^ i
  39882. // advance the sync point to a NAL start, if necessary
  39883. for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
  39884. if (buffer[syncPoint + 2] === 1) {
  39885. // the sync point is properly aligned
  39886. i = syncPoint + 5;
  39887. break;
  39888. }
  39889. }
  39890. while (i < buffer.byteLength) {
  39891. // look at the current byte to determine if we've hit the end of
  39892. // a NAL unit boundary
  39893. switch (buffer[i]) {
  39894. case 0:
  39895. // skip past non-sync sequences
  39896. if (buffer[i - 1] !== 0) {
  39897. i += 2;
  39898. break;
  39899. } else if (buffer[i - 2] !== 0) {
  39900. i++;
  39901. break;
  39902. } // deliver the NAL unit if it isn't empty
  39903. if (syncPoint + 3 !== i - 2) {
  39904. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  39905. } // drop trailing zeroes
  39906. do {
  39907. i++;
  39908. } while (buffer[i] !== 1 && i < buffer.length);
  39909. syncPoint = i - 2;
  39910. i += 3;
  39911. break;
  39912. case 1:
  39913. // skip past non-sync sequences
  39914. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  39915. i += 3;
  39916. break;
  39917. } // deliver the NAL unit
  39918. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  39919. syncPoint = i - 2;
  39920. i += 3;
  39921. break;
  39922. default:
  39923. // the current byte isn't a one or zero, so it cannot be part
  39924. // of a sync sequence
  39925. i += 3;
  39926. break;
  39927. }
  39928. } // filter out the NAL units that were delivered
  39929. buffer = buffer.subarray(syncPoint);
  39930. i -= syncPoint;
  39931. syncPoint = 0;
  39932. };
  39933. this.flush = function () {
  39934. // deliver the last buffered NAL unit
  39935. if (buffer && buffer.byteLength > 3) {
  39936. this.trigger('data', buffer.subarray(syncPoint + 3));
  39937. } // reset the stream state
  39938. buffer = null;
  39939. syncPoint = 0;
  39940. this.trigger('done');
  39941. };
  39942. };
  39943. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  39944. // see Recommendation ITU-T H.264 (4/2013),
  39945. // 7.3.2.1.1 Sequence parameter set data syntax
  39946. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  39947. 100: true,
  39948. 110: true,
  39949. 122: true,
  39950. 244: true,
  39951. 44: true,
  39952. 83: true,
  39953. 86: true,
  39954. 118: true,
  39955. 128: true,
  39956. 138: true,
  39957. 139: true,
  39958. 134: true
  39959. };
  39960. /**
  39961. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  39962. * events.
  39963. */
  39964. _H264Stream = function H264Stream() {
  39965. var nalByteStream = new _NalByteStream(),
  39966. self,
  39967. trackId,
  39968. currentPts,
  39969. currentDts,
  39970. discardEmulationPreventionBytes,
  39971. readSequenceParameterSet,
  39972. skipScalingList;
  39973. _H264Stream.prototype.init.call(this);
  39974. self = this;
  39975. /*
  39976. * Pushes a packet from a stream onto the NalByteStream
  39977. *
  39978. * @param {Object} packet - A packet received from a stream
  39979. * @param {Uint8Array} packet.data - The raw bytes of the packet
  39980. * @param {Number} packet.dts - Decode timestamp of the packet
  39981. * @param {Number} packet.pts - Presentation timestamp of the packet
  39982. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  39983. * @param {('video'|'audio')} packet.type - The type of packet
  39984. *
  39985. */
  39986. this.push = function (packet) {
  39987. if (packet.type !== 'video') {
  39988. return;
  39989. }
  39990. trackId = packet.trackId;
  39991. currentPts = packet.pts;
  39992. currentDts = packet.dts;
  39993. nalByteStream.push(packet);
  39994. };
  39995. /*
  39996. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  39997. * for the NALUs to the next stream component.
  39998. * Also, preprocess caption and sequence parameter NALUs.
  39999. *
  40000. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  40001. * @see NalByteStream.push
  40002. */
  40003. nalByteStream.on('data', function (data) {
  40004. var event = {
  40005. trackId: trackId,
  40006. pts: currentPts,
  40007. dts: currentDts,
  40008. data: data
  40009. };
  40010. switch (data[0] & 0x1f) {
  40011. case 0x05:
  40012. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  40013. break;
  40014. case 0x06:
  40015. event.nalUnitType = 'sei_rbsp';
  40016. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  40017. break;
  40018. case 0x07:
  40019. event.nalUnitType = 'seq_parameter_set_rbsp';
  40020. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  40021. event.config = readSequenceParameterSet(event.escapedRBSP);
  40022. break;
  40023. case 0x08:
  40024. event.nalUnitType = 'pic_parameter_set_rbsp';
  40025. break;
  40026. case 0x09:
  40027. event.nalUnitType = 'access_unit_delimiter_rbsp';
  40028. break;
  40029. default:
  40030. break;
  40031. } // This triggers data on the H264Stream
  40032. self.trigger('data', event);
  40033. });
  40034. nalByteStream.on('done', function () {
  40035. self.trigger('done');
  40036. });
  40037. this.flush = function () {
  40038. nalByteStream.flush();
  40039. };
  40040. /**
  40041. * Advance the ExpGolomb decoder past a scaling list. The scaling
  40042. * list is optionally transmitted as part of a sequence parameter
  40043. * set and is not relevant to transmuxing.
  40044. * @param count {number} the number of entries in this scaling list
  40045. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  40046. * start of a scaling list
  40047. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  40048. */
  40049. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  40050. var lastScale = 8,
  40051. nextScale = 8,
  40052. j,
  40053. deltaScale;
  40054. for (j = 0; j < count; j++) {
  40055. if (nextScale !== 0) {
  40056. deltaScale = expGolombDecoder.readExpGolomb();
  40057. nextScale = (lastScale + deltaScale + 256) % 256;
  40058. }
  40059. lastScale = nextScale === 0 ? lastScale : nextScale;
  40060. }
  40061. };
  40062. /**
  40063. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  40064. * Sequence Payload"
  40065. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  40066. * unit
  40067. * @return {Uint8Array} the RBSP without any Emulation
  40068. * Prevention Bytes
  40069. */
  40070. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  40071. var length = data.byteLength,
  40072. emulationPreventionBytesPositions = [],
  40073. i = 1,
  40074. newLength,
  40075. newData; // Find all `Emulation Prevention Bytes`
  40076. while (i < length - 2) {
  40077. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  40078. emulationPreventionBytesPositions.push(i + 2);
  40079. i += 2;
  40080. } else {
  40081. i++;
  40082. }
  40083. } // If no Emulation Prevention Bytes were found just return the original
  40084. // array
  40085. if (emulationPreventionBytesPositions.length === 0) {
  40086. return data;
  40087. } // Create a new array to hold the NAL unit data
  40088. newLength = length - emulationPreventionBytesPositions.length;
  40089. newData = new Uint8Array(newLength);
  40090. var sourceIndex = 0;
  40091. for (i = 0; i < newLength; sourceIndex++, i++) {
  40092. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  40093. // Skip this byte
  40094. sourceIndex++; // Remove this position index
  40095. emulationPreventionBytesPositions.shift();
  40096. }
  40097. newData[i] = data[sourceIndex];
  40098. }
  40099. return newData;
  40100. };
  40101. /**
  40102. * Read a sequence parameter set and return some interesting video
  40103. * properties. A sequence parameter set is the H264 metadata that
  40104. * describes the properties of upcoming video frames.
  40105. * @param data {Uint8Array} the bytes of a sequence parameter set
  40106. * @return {object} an object with configuration parsed from the
  40107. * sequence parameter set, including the dimensions of the
  40108. * associated video frames.
  40109. */
  40110. readSequenceParameterSet = function readSequenceParameterSet(data) {
  40111. var frameCropLeftOffset = 0,
  40112. frameCropRightOffset = 0,
  40113. frameCropTopOffset = 0,
  40114. frameCropBottomOffset = 0,
  40115. sarScale = 1,
  40116. expGolombDecoder,
  40117. profileIdc,
  40118. levelIdc,
  40119. profileCompatibility,
  40120. chromaFormatIdc,
  40121. picOrderCntType,
  40122. numRefFramesInPicOrderCntCycle,
  40123. picWidthInMbsMinus1,
  40124. picHeightInMapUnitsMinus1,
  40125. frameMbsOnlyFlag,
  40126. scalingListCount,
  40127. sarRatio,
  40128. aspectRatioIdc,
  40129. i;
  40130. expGolombDecoder = new expGolomb(data);
  40131. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  40132. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  40133. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  40134. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  40135. // some profiles have more optional data we don't need
  40136. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  40137. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  40138. if (chromaFormatIdc === 3) {
  40139. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  40140. }
  40141. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  40142. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  40143. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  40144. if (expGolombDecoder.readBoolean()) {
  40145. // seq_scaling_matrix_present_flag
  40146. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  40147. for (i = 0; i < scalingListCount; i++) {
  40148. if (expGolombDecoder.readBoolean()) {
  40149. // seq_scaling_list_present_flag[ i ]
  40150. if (i < 6) {
  40151. skipScalingList(16, expGolombDecoder);
  40152. } else {
  40153. skipScalingList(64, expGolombDecoder);
  40154. }
  40155. }
  40156. }
  40157. }
  40158. }
  40159. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  40160. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  40161. if (picOrderCntType === 0) {
  40162. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  40163. } else if (picOrderCntType === 1) {
  40164. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  40165. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  40166. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  40167. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  40168. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  40169. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  40170. }
  40171. }
  40172. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  40173. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  40174. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  40175. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  40176. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  40177. if (frameMbsOnlyFlag === 0) {
  40178. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  40179. }
  40180. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  40181. if (expGolombDecoder.readBoolean()) {
  40182. // frame_cropping_flag
  40183. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  40184. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  40185. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  40186. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  40187. }
  40188. if (expGolombDecoder.readBoolean()) {
  40189. // vui_parameters_present_flag
  40190. if (expGolombDecoder.readBoolean()) {
  40191. // aspect_ratio_info_present_flag
  40192. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  40193. switch (aspectRatioIdc) {
  40194. case 1:
  40195. sarRatio = [1, 1];
  40196. break;
  40197. case 2:
  40198. sarRatio = [12, 11];
  40199. break;
  40200. case 3:
  40201. sarRatio = [10, 11];
  40202. break;
  40203. case 4:
  40204. sarRatio = [16, 11];
  40205. break;
  40206. case 5:
  40207. sarRatio = [40, 33];
  40208. break;
  40209. case 6:
  40210. sarRatio = [24, 11];
  40211. break;
  40212. case 7:
  40213. sarRatio = [20, 11];
  40214. break;
  40215. case 8:
  40216. sarRatio = [32, 11];
  40217. break;
  40218. case 9:
  40219. sarRatio = [80, 33];
  40220. break;
  40221. case 10:
  40222. sarRatio = [18, 11];
  40223. break;
  40224. case 11:
  40225. sarRatio = [15, 11];
  40226. break;
  40227. case 12:
  40228. sarRatio = [64, 33];
  40229. break;
  40230. case 13:
  40231. sarRatio = [160, 99];
  40232. break;
  40233. case 14:
  40234. sarRatio = [4, 3];
  40235. break;
  40236. case 15:
  40237. sarRatio = [3, 2];
  40238. break;
  40239. case 16:
  40240. sarRatio = [2, 1];
  40241. break;
  40242. case 255:
  40243. {
  40244. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  40245. break;
  40246. }
  40247. }
  40248. if (sarRatio) {
  40249. sarScale = sarRatio[0] / sarRatio[1];
  40250. }
  40251. }
  40252. }
  40253. return {
  40254. profileIdc: profileIdc,
  40255. levelIdc: levelIdc,
  40256. profileCompatibility: profileCompatibility,
  40257. width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  40258. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
  40259. };
  40260. };
  40261. };
  40262. _H264Stream.prototype = new stream();
  40263. var h264 = {
  40264. H264Stream: _H264Stream,
  40265. NalByteStream: _NalByteStream
  40266. };
  40267. /**
  40268. * mux.js
  40269. *
  40270. * Copyright (c) Brightcove
  40271. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  40272. *
  40273. * Utilities to detect basic properties and metadata about Aac data.
  40274. */
  40275. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  40276. var isLikelyAacData = function isLikelyAacData(data) {
  40277. if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
  40278. return true;
  40279. }
  40280. return false;
  40281. };
  40282. var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  40283. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  40284. }; // return a percent-encoded representation of the specified byte range
  40285. // @see http://en.wikipedia.org/wiki/Percent-encoding
  40286. var percentEncode$1 = function percentEncode(bytes, start, end) {
  40287. var i,
  40288. result = '';
  40289. for (i = start; i < end; i++) {
  40290. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  40291. }
  40292. return result;
  40293. }; // return the string representation of the specified byte range,
  40294. // interpreted as ISO-8859-1.
  40295. var parseIso88591$1 = function parseIso88591(bytes, start, end) {
  40296. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  40297. };
  40298. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  40299. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  40300. flags = header[byteIndex + 5],
  40301. footerPresent = (flags & 16) >> 4;
  40302. if (footerPresent) {
  40303. return returnSize + 20;
  40304. }
  40305. return returnSize + 10;
  40306. };
  40307. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  40308. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  40309. middle = header[byteIndex + 4] << 3,
  40310. highTwo = header[byteIndex + 3] & 0x3 << 11;
  40311. return highTwo | middle | lowThree;
  40312. };
  40313. var parseType$1 = function parseType(header, byteIndex) {
  40314. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  40315. return 'timed-metadata';
  40316. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  40317. return 'audio';
  40318. }
  40319. return null;
  40320. };
  40321. var parseSampleRate = function parseSampleRate(packet) {
  40322. var i = 0;
  40323. while (i + 5 < packet.length) {
  40324. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  40325. // If a valid header was not found, jump one forward and attempt to
  40326. // find a valid ADTS header starting at the next byte
  40327. i++;
  40328. continue;
  40329. }
  40330. return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
  40331. }
  40332. return null;
  40333. };
  40334. var parseAacTimestamp = function parseAacTimestamp(packet) {
  40335. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  40336. frameStart = 10;
  40337. if (packet[5] & 0x40) {
  40338. // advance the frame start past the extended header
  40339. frameStart += 4; // header size field
  40340. frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
  40341. } // parse one or more ID3 frames
  40342. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  40343. do {
  40344. // determine the number of bytes in this frame
  40345. frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
  40346. if (frameSize < 1) {
  40347. return null;
  40348. }
  40349. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  40350. if (frameHeader === 'PRIV') {
  40351. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  40352. for (var i = 0; i < frame.byteLength; i++) {
  40353. if (frame[i] === 0) {
  40354. var owner = parseIso88591$1(frame, 0, i);
  40355. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  40356. var d = frame.subarray(i + 1);
  40357. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  40358. size *= 4;
  40359. size += d[7] & 0x03;
  40360. return size;
  40361. }
  40362. break;
  40363. }
  40364. }
  40365. }
  40366. frameStart += 10; // advance past the frame header
  40367. frameStart += frameSize; // advance past the frame body
  40368. } while (frameStart < packet.byteLength);
  40369. return null;
  40370. };
  40371. var utils = {
  40372. isLikelyAacData: isLikelyAacData,
  40373. parseId3TagSize: parseId3TagSize,
  40374. parseAdtsSize: parseAdtsSize,
  40375. parseType: parseType$1,
  40376. parseSampleRate: parseSampleRate,
  40377. parseAacTimestamp: parseAacTimestamp
  40378. }; // Constants
  40379. var _AacStream;
  40380. /**
  40381. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  40382. */
  40383. _AacStream = function AacStream() {
  40384. var everything = new Uint8Array(),
  40385. timeStamp = 0;
  40386. _AacStream.prototype.init.call(this);
  40387. this.setTimestamp = function (timestamp) {
  40388. timeStamp = timestamp;
  40389. };
  40390. this.push = function (bytes) {
  40391. var frameSize = 0,
  40392. byteIndex = 0,
  40393. bytesLeft,
  40394. chunk,
  40395. packet,
  40396. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  40397. // bytes that were pushed in
  40398. if (everything.length) {
  40399. tempLength = everything.length;
  40400. everything = new Uint8Array(bytes.byteLength + tempLength);
  40401. everything.set(everything.subarray(0, tempLength));
  40402. everything.set(bytes, tempLength);
  40403. } else {
  40404. everything = bytes;
  40405. }
  40406. while (everything.length - byteIndex >= 3) {
  40407. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  40408. // Exit early because we don't have enough to parse
  40409. // the ID3 tag header
  40410. if (everything.length - byteIndex < 10) {
  40411. break;
  40412. } // check framesize
  40413. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  40414. // to emit a full packet
  40415. // Add to byteIndex to support multiple ID3 tags in sequence
  40416. if (byteIndex + frameSize > everything.length) {
  40417. break;
  40418. }
  40419. chunk = {
  40420. type: 'timed-metadata',
  40421. data: everything.subarray(byteIndex, byteIndex + frameSize)
  40422. };
  40423. this.trigger('data', chunk);
  40424. byteIndex += frameSize;
  40425. continue;
  40426. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  40427. // Exit early because we don't have enough to parse
  40428. // the ADTS frame header
  40429. if (everything.length - byteIndex < 7) {
  40430. break;
  40431. }
  40432. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  40433. // to emit a full packet
  40434. if (byteIndex + frameSize > everything.length) {
  40435. break;
  40436. }
  40437. packet = {
  40438. type: 'audio',
  40439. data: everything.subarray(byteIndex, byteIndex + frameSize),
  40440. pts: timeStamp,
  40441. dts: timeStamp
  40442. };
  40443. this.trigger('data', packet);
  40444. byteIndex += frameSize;
  40445. continue;
  40446. }
  40447. byteIndex++;
  40448. }
  40449. bytesLeft = everything.length - byteIndex;
  40450. if (bytesLeft > 0) {
  40451. everything = everything.subarray(byteIndex);
  40452. } else {
  40453. everything = new Uint8Array();
  40454. }
  40455. };
  40456. };
  40457. _AacStream.prototype = new stream();
  40458. var aac = _AacStream;
  40459. var H264Stream = h264.H264Stream;
  40460. var isLikelyAacData$1 = utils.isLikelyAacData; // constants
  40461. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  40462. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
  40463. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  40464. /**
  40465. * Compare two arrays (even typed) for same-ness
  40466. */
  40467. var arrayEquals = function arrayEquals(a, b) {
  40468. var i;
  40469. if (a.length !== b.length) {
  40470. return false;
  40471. } // compare the value of each element in the array
  40472. for (i = 0; i < a.length; i++) {
  40473. if (a[i] !== b[i]) {
  40474. return false;
  40475. }
  40476. }
  40477. return true;
  40478. };
  40479. var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  40480. var ptsOffsetFromDts = startPts - startDts,
  40481. decodeDuration = endDts - startDts,
  40482. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  40483. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  40484. // In order to provide relevant values for the player times, base timing info on the
  40485. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  40486. return {
  40487. start: {
  40488. dts: baseMediaDecodeTime,
  40489. pts: baseMediaDecodeTime + ptsOffsetFromDts
  40490. },
  40491. end: {
  40492. dts: baseMediaDecodeTime + decodeDuration,
  40493. pts: baseMediaDecodeTime + presentationDuration
  40494. },
  40495. prependedContentDuration: prependedContentDuration,
  40496. baseMediaDecodeTime: baseMediaDecodeTime
  40497. };
  40498. };
  40499. /**
  40500. * Constructs a single-track, ISO BMFF media segment from AAC data
  40501. * events. The output of this stream can be fed to a SourceBuffer
  40502. * configured with a suitable initialization segment.
  40503. * @param track {object} track metadata configuration
  40504. * @param options {object} transmuxer options object
  40505. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  40506. * in the source; false to adjust the first segment to start at 0.
  40507. */
  40508. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  40509. var adtsFrames = [],
  40510. sequenceNumber = 0,
  40511. earliestAllowedDts = 0,
  40512. audioAppendStartTs = 0,
  40513. videoBaseMediaDecodeTime = Infinity;
  40514. options = options || {};
  40515. _AudioSegmentStream.prototype.init.call(this);
  40516. this.push = function (data) {
  40517. trackDecodeInfo.collectDtsInfo(track, data);
  40518. if (track) {
  40519. AUDIO_PROPERTIES.forEach(function (prop) {
  40520. track[prop] = data[prop];
  40521. });
  40522. } // buffer audio data until end() is called
  40523. adtsFrames.push(data);
  40524. };
  40525. this.setEarliestDts = function (earliestDts) {
  40526. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  40527. };
  40528. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  40529. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  40530. };
  40531. this.setAudioAppendStart = function (timestamp) {
  40532. audioAppendStartTs = timestamp;
  40533. };
  40534. this.flush = function () {
  40535. var frames, moof, mdat, boxes; // return early if no audio data has been observed
  40536. if (adtsFrames.length === 0) {
  40537. this.trigger('done', 'AudioSegmentStream');
  40538. return;
  40539. }
  40540. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  40541. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  40542. audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  40543. // samples (that is, adts frames) in the audio data
  40544. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  40545. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  40546. adtsFrames = [];
  40547. moof = mp4Generator.moof(sequenceNumber, [track]);
  40548. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  40549. sequenceNumber++;
  40550. boxes.set(moof);
  40551. boxes.set(mdat, moof.byteLength);
  40552. trackDecodeInfo.clearDtsInfo(track);
  40553. this.trigger('data', {
  40554. track: track,
  40555. boxes: boxes
  40556. });
  40557. this.trigger('done', 'AudioSegmentStream');
  40558. };
  40559. };
  40560. _AudioSegmentStream.prototype = new stream();
  40561. /**
  40562. * Constructs a single-track, ISO BMFF media segment from H264 data
  40563. * events. The output of this stream can be fed to a SourceBuffer
  40564. * configured with a suitable initialization segment.
  40565. * @param track {object} track metadata configuration
  40566. * @param options {object} transmuxer options object
  40567. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  40568. * gopsToAlignWith list when attempting to align gop pts
  40569. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  40570. * in the source; false to adjust the first segment to start at 0.
  40571. */
  40572. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  40573. var sequenceNumber = 0,
  40574. nalUnits = [],
  40575. gopsToAlignWith = [],
  40576. config,
  40577. pps;
  40578. options = options || {};
  40579. _VideoSegmentStream.prototype.init.call(this);
  40580. delete track.minPTS;
  40581. this.gopCache_ = [];
  40582. /**
  40583. * Constructs a ISO BMFF segment given H264 nalUnits
  40584. * @param {Object} nalUnit A data event representing a nalUnit
  40585. * @param {String} nalUnit.nalUnitType
  40586. * @param {Object} nalUnit.config Properties for a mp4 track
  40587. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  40588. * @see lib/codecs/h264.js
  40589. **/
  40590. this.push = function (nalUnit) {
  40591. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  40592. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  40593. config = nalUnit.config;
  40594. track.sps = [nalUnit.data];
  40595. VIDEO_PROPERTIES.forEach(function (prop) {
  40596. track[prop] = config[prop];
  40597. }, this);
  40598. }
  40599. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  40600. pps = nalUnit.data;
  40601. track.pps = [nalUnit.data];
  40602. } // buffer video until flush() is called
  40603. nalUnits.push(nalUnit);
  40604. };
  40605. /**
  40606. * Pass constructed ISO BMFF track and boxes on to the
  40607. * next stream in the pipeline
  40608. **/
  40609. this.flush = function () {
  40610. var frames,
  40611. gopForFusion,
  40612. gops,
  40613. moof,
  40614. mdat,
  40615. boxes,
  40616. prependedContentDuration = 0,
  40617. firstGop,
  40618. lastGop; // Throw away nalUnits at the start of the byte stream until
  40619. // we find the first AUD
  40620. while (nalUnits.length) {
  40621. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  40622. break;
  40623. }
  40624. nalUnits.shift();
  40625. } // Return early if no video data has been observed
  40626. if (nalUnits.length === 0) {
  40627. this.resetStream_();
  40628. this.trigger('done', 'VideoSegmentStream');
  40629. return;
  40630. } // Organize the raw nal-units into arrays that represent
  40631. // higher-level constructs such as frames and gops
  40632. // (group-of-pictures)
  40633. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  40634. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  40635. // a problem since MSE (on Chrome) requires a leading keyframe.
  40636. //
  40637. // We have two approaches to repairing this situation:
  40638. // 1) GOP-FUSION:
  40639. // This is where we keep track of the GOPS (group-of-pictures)
  40640. // from previous fragments and attempt to find one that we can
  40641. // prepend to the current fragment in order to create a valid
  40642. // fragment.
  40643. // 2) KEYFRAME-PULLING:
  40644. // Here we search for the first keyframe in the fragment and
  40645. // throw away all the frames between the start of the fragment
  40646. // and that keyframe. We then extend the duration and pull the
  40647. // PTS of the keyframe forward so that it covers the time range
  40648. // of the frames that were disposed of.
  40649. //
  40650. // #1 is far prefereable over #2 which can cause "stuttering" but
  40651. // requires more things to be just right.
  40652. if (!gops[0][0].keyFrame) {
  40653. // Search for a gop for fusion from our gopCache
  40654. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  40655. if (gopForFusion) {
  40656. // in order to provide more accurate timing information about the segment, save
  40657. // the number of seconds prepended to the original segment due to GOP fusion
  40658. prependedContentDuration = gopForFusion.duration;
  40659. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  40660. // new gop at the beginning
  40661. gops.byteLength += gopForFusion.byteLength;
  40662. gops.nalCount += gopForFusion.nalCount;
  40663. gops.pts = gopForFusion.pts;
  40664. gops.dts = gopForFusion.dts;
  40665. gops.duration += gopForFusion.duration;
  40666. } else {
  40667. // If we didn't find a candidate gop fall back to keyframe-pulling
  40668. gops = frameUtils.extendFirstKeyFrame(gops);
  40669. }
  40670. } // Trim gops to align with gopsToAlignWith
  40671. if (gopsToAlignWith.length) {
  40672. var alignedGops;
  40673. if (options.alignGopsAtEnd) {
  40674. alignedGops = this.alignGopsAtEnd_(gops);
  40675. } else {
  40676. alignedGops = this.alignGopsAtStart_(gops);
  40677. }
  40678. if (!alignedGops) {
  40679. // save all the nals in the last GOP into the gop cache
  40680. this.gopCache_.unshift({
  40681. gop: gops.pop(),
  40682. pps: track.pps,
  40683. sps: track.sps
  40684. }); // Keep a maximum of 6 GOPs in the cache
  40685. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  40686. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  40687. this.resetStream_();
  40688. this.trigger('done', 'VideoSegmentStream');
  40689. return;
  40690. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  40691. // when recalculated before sending off to CoalesceStream
  40692. trackDecodeInfo.clearDtsInfo(track);
  40693. gops = alignedGops;
  40694. }
  40695. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  40696. // samples (that is, frames) in the video data
  40697. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  40698. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  40699. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  40700. this.trigger('processedGopsInfo', gops.map(function (gop) {
  40701. return {
  40702. pts: gop.pts,
  40703. dts: gop.dts,
  40704. byteLength: gop.byteLength
  40705. };
  40706. }));
  40707. firstGop = gops[0];
  40708. lastGop = gops[gops.length - 1];
  40709. 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
  40710. this.gopCache_.unshift({
  40711. gop: gops.pop(),
  40712. pps: track.pps,
  40713. sps: track.sps
  40714. }); // Keep a maximum of 6 GOPs in the cache
  40715. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  40716. nalUnits = [];
  40717. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  40718. this.trigger('timelineStartInfo', track.timelineStartInfo);
  40719. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  40720. // throwing away hundreds of media segment fragments
  40721. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  40722. sequenceNumber++;
  40723. boxes.set(moof);
  40724. boxes.set(mdat, moof.byteLength);
  40725. this.trigger('data', {
  40726. track: track,
  40727. boxes: boxes
  40728. });
  40729. this.resetStream_(); // Continue with the flush process now
  40730. this.trigger('done', 'VideoSegmentStream');
  40731. };
  40732. this.resetStream_ = function () {
  40733. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  40734. // for instance, when we are rendition switching
  40735. config = undefined;
  40736. pps = undefined;
  40737. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  40738. // return it or return null if no good candidate was found
  40739. this.getGopForFusion_ = function (nalUnit) {
  40740. var halfSecond = 45000,
  40741. // Half-a-second in a 90khz clock
  40742. allowableOverlap = 10000,
  40743. // About 3 frames @ 30fps
  40744. nearestDistance = Infinity,
  40745. dtsDistance,
  40746. nearestGopObj,
  40747. currentGop,
  40748. currentGopObj,
  40749. i; // Search for the GOP nearest to the beginning of this nal unit
  40750. for (i = 0; i < this.gopCache_.length; i++) {
  40751. currentGopObj = this.gopCache_[i];
  40752. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  40753. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  40754. continue;
  40755. } // Reject Gops that would require a negative baseMediaDecodeTime
  40756. if (currentGop.dts < track.timelineStartInfo.dts) {
  40757. continue;
  40758. } // The distance between the end of the gop and the start of the nalUnit
  40759. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  40760. // a half-second of the nal unit
  40761. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  40762. // Always use the closest GOP we found if there is more than
  40763. // one candidate
  40764. if (!nearestGopObj || nearestDistance > dtsDistance) {
  40765. nearestGopObj = currentGopObj;
  40766. nearestDistance = dtsDistance;
  40767. }
  40768. }
  40769. }
  40770. if (nearestGopObj) {
  40771. return nearestGopObj.gop;
  40772. }
  40773. return null;
  40774. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  40775. // of gopsToAlignWith starting from the START of the list
  40776. this.alignGopsAtStart_ = function (gops) {
  40777. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  40778. byteLength = gops.byteLength;
  40779. nalCount = gops.nalCount;
  40780. duration = gops.duration;
  40781. alignIndex = gopIndex = 0;
  40782. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  40783. align = gopsToAlignWith[alignIndex];
  40784. gop = gops[gopIndex];
  40785. if (align.pts === gop.pts) {
  40786. break;
  40787. }
  40788. if (gop.pts > align.pts) {
  40789. // this current gop starts after the current gop we want to align on, so increment
  40790. // align index
  40791. alignIndex++;
  40792. continue;
  40793. } // current gop starts before the current gop we want to align on. so increment gop
  40794. // index
  40795. gopIndex++;
  40796. byteLength -= gop.byteLength;
  40797. nalCount -= gop.nalCount;
  40798. duration -= gop.duration;
  40799. }
  40800. if (gopIndex === 0) {
  40801. // no gops to trim
  40802. return gops;
  40803. }
  40804. if (gopIndex === gops.length) {
  40805. // all gops trimmed, skip appending all gops
  40806. return null;
  40807. }
  40808. alignedGops = gops.slice(gopIndex);
  40809. alignedGops.byteLength = byteLength;
  40810. alignedGops.duration = duration;
  40811. alignedGops.nalCount = nalCount;
  40812. alignedGops.pts = alignedGops[0].pts;
  40813. alignedGops.dts = alignedGops[0].dts;
  40814. return alignedGops;
  40815. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  40816. // of gopsToAlignWith starting from the END of the list
  40817. this.alignGopsAtEnd_ = function (gops) {
  40818. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  40819. alignIndex = gopsToAlignWith.length - 1;
  40820. gopIndex = gops.length - 1;
  40821. alignEndIndex = null;
  40822. matchFound = false;
  40823. while (alignIndex >= 0 && gopIndex >= 0) {
  40824. align = gopsToAlignWith[alignIndex];
  40825. gop = gops[gopIndex];
  40826. if (align.pts === gop.pts) {
  40827. matchFound = true;
  40828. break;
  40829. }
  40830. if (align.pts > gop.pts) {
  40831. alignIndex--;
  40832. continue;
  40833. }
  40834. if (alignIndex === gopsToAlignWith.length - 1) {
  40835. // gop.pts is greater than the last alignment candidate. If no match is found
  40836. // by the end of this loop, we still want to append gops that come after this
  40837. // point
  40838. alignEndIndex = gopIndex;
  40839. }
  40840. gopIndex--;
  40841. }
  40842. if (!matchFound && alignEndIndex === null) {
  40843. return null;
  40844. }
  40845. var trimIndex;
  40846. if (matchFound) {
  40847. trimIndex = gopIndex;
  40848. } else {
  40849. trimIndex = alignEndIndex;
  40850. }
  40851. if (trimIndex === 0) {
  40852. return gops;
  40853. }
  40854. var alignedGops = gops.slice(trimIndex);
  40855. var metadata = alignedGops.reduce(function (total, gop) {
  40856. total.byteLength += gop.byteLength;
  40857. total.duration += gop.duration;
  40858. total.nalCount += gop.nalCount;
  40859. return total;
  40860. }, {
  40861. byteLength: 0,
  40862. duration: 0,
  40863. nalCount: 0
  40864. });
  40865. alignedGops.byteLength = metadata.byteLength;
  40866. alignedGops.duration = metadata.duration;
  40867. alignedGops.nalCount = metadata.nalCount;
  40868. alignedGops.pts = alignedGops[0].pts;
  40869. alignedGops.dts = alignedGops[0].dts;
  40870. return alignedGops;
  40871. };
  40872. this.alignGopsWith = function (newGopsToAlignWith) {
  40873. gopsToAlignWith = newGopsToAlignWith;
  40874. };
  40875. };
  40876. _VideoSegmentStream.prototype = new stream();
  40877. /**
  40878. * A Stream that can combine multiple streams (ie. audio & video)
  40879. * into a single output segment for MSE. Also supports audio-only
  40880. * and video-only streams.
  40881. * @param options {object} transmuxer options object
  40882. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  40883. * in the source; false to adjust the first segment to start at media timeline start.
  40884. */
  40885. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  40886. // Number of Tracks per output segment
  40887. // If greater than 1, we combine multiple
  40888. // tracks into a single segment
  40889. this.numberOfTracks = 0;
  40890. this.metadataStream = metadataStream;
  40891. options = options || {};
  40892. if (typeof options.remux !== 'undefined') {
  40893. this.remuxTracks = !!options.remux;
  40894. } else {
  40895. this.remuxTracks = true;
  40896. }
  40897. if (typeof options.keepOriginalTimestamps === 'boolean') {
  40898. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  40899. }
  40900. this.pendingTracks = [];
  40901. this.videoTrack = null;
  40902. this.pendingBoxes = [];
  40903. this.pendingCaptions = [];
  40904. this.pendingMetadata = [];
  40905. this.pendingBytes = 0;
  40906. this.emittedTracks = 0;
  40907. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  40908. this.push = function (output) {
  40909. // buffer incoming captions until the associated video segment
  40910. // finishes
  40911. if (output.text) {
  40912. return this.pendingCaptions.push(output);
  40913. } // buffer incoming id3 tags until the final flush
  40914. if (output.frames) {
  40915. return this.pendingMetadata.push(output);
  40916. } // Add this track to the list of pending tracks and store
  40917. // important information required for the construction of
  40918. // the final segment
  40919. this.pendingTracks.push(output.track);
  40920. this.pendingBoxes.push(output.boxes);
  40921. this.pendingBytes += output.boxes.byteLength;
  40922. if (output.track.type === 'video') {
  40923. this.videoTrack = output.track;
  40924. }
  40925. if (output.track.type === 'audio') {
  40926. this.audioTrack = output.track;
  40927. }
  40928. };
  40929. };
  40930. _CoalesceStream.prototype = new stream();
  40931. _CoalesceStream.prototype.flush = function (flushSource) {
  40932. var offset = 0,
  40933. event = {
  40934. captions: [],
  40935. captionStreams: {},
  40936. metadata: [],
  40937. info: {}
  40938. },
  40939. caption,
  40940. id3,
  40941. initSegment,
  40942. timelineStartPts = 0,
  40943. i;
  40944. if (this.pendingTracks.length < this.numberOfTracks) {
  40945. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  40946. // Return because we haven't received a flush from a data-generating
  40947. // portion of the segment (meaning that we have only recieved meta-data
  40948. // or captions.)
  40949. return;
  40950. } else if (this.remuxTracks) {
  40951. // Return until we have enough tracks from the pipeline to remux (if we
  40952. // are remuxing audio and video into a single MP4)
  40953. return;
  40954. } else if (this.pendingTracks.length === 0) {
  40955. // In the case where we receive a flush without any data having been
  40956. // received we consider it an emitted track for the purposes of coalescing
  40957. // `done` events.
  40958. // We do this for the case where there is an audio and video track in the
  40959. // segment but no audio data. (seen in several playlists with alternate
  40960. // audio tracks and no audio present in the main TS segments.)
  40961. this.emittedTracks++;
  40962. if (this.emittedTracks >= this.numberOfTracks) {
  40963. this.trigger('done');
  40964. this.emittedTracks = 0;
  40965. }
  40966. return;
  40967. }
  40968. }
  40969. if (this.videoTrack) {
  40970. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  40971. VIDEO_PROPERTIES.forEach(function (prop) {
  40972. event.info[prop] = this.videoTrack[prop];
  40973. }, this);
  40974. } else if (this.audioTrack) {
  40975. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  40976. AUDIO_PROPERTIES.forEach(function (prop) {
  40977. event.info[prop] = this.audioTrack[prop];
  40978. }, this);
  40979. }
  40980. if (this.pendingTracks.length === 1) {
  40981. event.type = this.pendingTracks[0].type;
  40982. } else {
  40983. event.type = 'combined';
  40984. }
  40985. this.emittedTracks += this.pendingTracks.length;
  40986. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  40987. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  40988. // and track definitions
  40989. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  40990. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  40991. for (i = 0; i < this.pendingBoxes.length; i++) {
  40992. event.data.set(this.pendingBoxes[i], offset);
  40993. offset += this.pendingBoxes[i].byteLength;
  40994. } // Translate caption PTS times into second offsets to match the
  40995. // video timeline for the segment, and add track info
  40996. for (i = 0; i < this.pendingCaptions.length; i++) {
  40997. caption = this.pendingCaptions[i];
  40998. caption.startTime = caption.startPts;
  40999. if (!this.keepOriginalTimestamps) {
  41000. caption.startTime -= timelineStartPts;
  41001. }
  41002. caption.startTime /= 90e3;
  41003. caption.endTime = caption.endPts;
  41004. if (!this.keepOriginalTimestamps) {
  41005. caption.endTime -= timelineStartPts;
  41006. }
  41007. caption.endTime /= 90e3;
  41008. event.captionStreams[caption.stream] = true;
  41009. event.captions.push(caption);
  41010. } // Translate ID3 frame PTS times into second offsets to match the
  41011. // video timeline for the segment
  41012. for (i = 0; i < this.pendingMetadata.length; i++) {
  41013. id3 = this.pendingMetadata[i];
  41014. id3.cueTime = id3.pts;
  41015. if (!this.keepOriginalTimestamps) {
  41016. id3.cueTime -= timelineStartPts;
  41017. }
  41018. id3.cueTime /= 90e3;
  41019. event.metadata.push(id3);
  41020. } // We add this to every single emitted segment even though we only need
  41021. // it for the first
  41022. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  41023. this.pendingTracks.length = 0;
  41024. this.videoTrack = null;
  41025. this.pendingBoxes.length = 0;
  41026. this.pendingCaptions.length = 0;
  41027. this.pendingBytes = 0;
  41028. this.pendingMetadata.length = 0; // Emit the built segment
  41029. this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
  41030. if (this.emittedTracks >= this.numberOfTracks) {
  41031. this.trigger('done');
  41032. this.emittedTracks = 0;
  41033. }
  41034. };
  41035. /**
  41036. * A Stream that expects MP2T binary data as input and produces
  41037. * corresponding media segments, suitable for use with Media Source
  41038. * Extension (MSE) implementations that support the ISO BMFF byte
  41039. * stream format, like Chrome.
  41040. */
  41041. _Transmuxer = function Transmuxer(options) {
  41042. var self = this,
  41043. hasFlushed = true,
  41044. videoTrack,
  41045. audioTrack;
  41046. _Transmuxer.prototype.init.call(this);
  41047. options = options || {};
  41048. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  41049. this.transmuxPipeline_ = {};
  41050. this.setupAacPipeline = function () {
  41051. var pipeline = {};
  41052. this.transmuxPipeline_ = pipeline;
  41053. pipeline.type = 'aac';
  41054. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  41055. pipeline.aacStream = new aac();
  41056. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  41057. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  41058. pipeline.adtsStream = new adts();
  41059. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  41060. pipeline.headOfPipeline = pipeline.aacStream;
  41061. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  41062. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  41063. pipeline.metadataStream.on('timestamp', function (frame) {
  41064. pipeline.aacStream.setTimestamp(frame.timeStamp);
  41065. });
  41066. pipeline.aacStream.on('data', function (data) {
  41067. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  41068. audioTrack = audioTrack || {
  41069. timelineStartInfo: {
  41070. baseMediaDecodeTime: self.baseMediaDecodeTime
  41071. },
  41072. codec: 'adts',
  41073. type: 'audio'
  41074. }; // hook up the audio segment stream to the first track with aac data
  41075. pipeline.coalesceStream.numberOfTracks++;
  41076. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  41077. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  41078. }
  41079. }); // Re-emit any data coming from the coalesce stream to the outside world
  41080. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  41081. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  41082. };
  41083. this.setupTsPipeline = function () {
  41084. var pipeline = {};
  41085. this.transmuxPipeline_ = pipeline;
  41086. pipeline.type = 'ts';
  41087. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  41088. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  41089. pipeline.parseStream = new m2ts_1.TransportParseStream();
  41090. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  41091. pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  41092. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  41093. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  41094. pipeline.adtsStream = new adts();
  41095. pipeline.h264Stream = new H264Stream();
  41096. pipeline.captionStream = new m2ts_1.CaptionStream();
  41097. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  41098. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  41099. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  41100. // demux the streams
  41101. pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
  41102. pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  41103. pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  41104. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  41105. pipeline.elementaryStream.on('data', function (data) {
  41106. var i;
  41107. if (data.type === 'metadata') {
  41108. i = data.tracks.length; // scan the tracks listed in the metadata
  41109. while (i--) {
  41110. if (!videoTrack && data.tracks[i].type === 'video') {
  41111. videoTrack = data.tracks[i];
  41112. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  41113. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  41114. audioTrack = data.tracks[i];
  41115. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  41116. }
  41117. } // hook up the video segment stream to the first track with h264 data
  41118. if (videoTrack && !pipeline.videoSegmentStream) {
  41119. pipeline.coalesceStream.numberOfTracks++;
  41120. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  41121. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  41122. // When video emits timelineStartInfo data after a flush, we forward that
  41123. // info to the AudioSegmentStream, if it exists, because video timeline
  41124. // data takes precedence.
  41125. if (audioTrack) {
  41126. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  41127. // very earliest DTS we have seen in video because Chrome will
  41128. // interpret any video track with a baseMediaDecodeTime that is
  41129. // non-zero as a gap.
  41130. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  41131. }
  41132. });
  41133. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  41134. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  41135. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  41136. if (audioTrack) {
  41137. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  41138. }
  41139. }); // Set up the final part of the video pipeline
  41140. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  41141. }
  41142. if (audioTrack && !pipeline.audioSegmentStream) {
  41143. // hook up the audio segment stream to the first track with aac data
  41144. pipeline.coalesceStream.numberOfTracks++;
  41145. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  41146. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  41147. }
  41148. }
  41149. }); // Re-emit any data coming from the coalesce stream to the outside world
  41150. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  41151. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  41152. }; // hook up the segment streams once track metadata is delivered
  41153. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  41154. var pipeline = this.transmuxPipeline_;
  41155. if (!options.keepOriginalTimestamps) {
  41156. this.baseMediaDecodeTime = baseMediaDecodeTime;
  41157. }
  41158. if (audioTrack) {
  41159. audioTrack.timelineStartInfo.dts = undefined;
  41160. audioTrack.timelineStartInfo.pts = undefined;
  41161. trackDecodeInfo.clearDtsInfo(audioTrack);
  41162. if (!options.keepOriginalTimestamps) {
  41163. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  41164. }
  41165. if (pipeline.audioTimestampRolloverStream) {
  41166. pipeline.audioTimestampRolloverStream.discontinuity();
  41167. }
  41168. }
  41169. if (videoTrack) {
  41170. if (pipeline.videoSegmentStream) {
  41171. pipeline.videoSegmentStream.gopCache_ = [];
  41172. pipeline.videoTimestampRolloverStream.discontinuity();
  41173. }
  41174. videoTrack.timelineStartInfo.dts = undefined;
  41175. videoTrack.timelineStartInfo.pts = undefined;
  41176. trackDecodeInfo.clearDtsInfo(videoTrack);
  41177. pipeline.captionStream.reset();
  41178. if (!options.keepOriginalTimestamps) {
  41179. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  41180. }
  41181. }
  41182. if (pipeline.timedMetadataTimestampRolloverStream) {
  41183. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  41184. }
  41185. };
  41186. this.setAudioAppendStart = function (timestamp) {
  41187. if (audioTrack) {
  41188. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  41189. }
  41190. };
  41191. this.alignGopsWith = function (gopsToAlignWith) {
  41192. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  41193. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  41194. }
  41195. }; // feed incoming data to the front of the parsing pipeline
  41196. this.push = function (data) {
  41197. if (hasFlushed) {
  41198. var isAac = isLikelyAacData$1(data);
  41199. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  41200. this.setupAacPipeline();
  41201. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  41202. this.setupTsPipeline();
  41203. }
  41204. hasFlushed = false;
  41205. }
  41206. this.transmuxPipeline_.headOfPipeline.push(data);
  41207. }; // flush any buffered data
  41208. this.flush = function () {
  41209. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  41210. this.transmuxPipeline_.headOfPipeline.flush();
  41211. }; // Caption data has to be reset when seeking outside buffered range
  41212. this.resetCaptions = function () {
  41213. if (this.transmuxPipeline_.captionStream) {
  41214. this.transmuxPipeline_.captionStream.reset();
  41215. }
  41216. };
  41217. };
  41218. _Transmuxer.prototype = new stream();
  41219. var transmuxer = {
  41220. Transmuxer: _Transmuxer,
  41221. VideoSegmentStream: _VideoSegmentStream,
  41222. AudioSegmentStream: _AudioSegmentStream,
  41223. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  41224. VIDEO_PROPERTIES: VIDEO_PROPERTIES,
  41225. // exported for testing
  41226. generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
  41227. };
  41228. var inspectMp4,
  41229. _textifyMp,
  41230. parseType$2 = probe.parseType,
  41231. parseMp4Date = function parseMp4Date(seconds) {
  41232. return new Date(seconds * 1000 - 2082844800000);
  41233. },
  41234. parseSampleFlags = function parseSampleFlags(flags) {
  41235. return {
  41236. isLeading: (flags[0] & 0x0c) >>> 2,
  41237. dependsOn: flags[0] & 0x03,
  41238. isDependedOn: (flags[1] & 0xc0) >>> 6,
  41239. hasRedundancy: (flags[1] & 0x30) >>> 4,
  41240. paddingValue: (flags[1] & 0x0e) >>> 1,
  41241. isNonSyncSample: flags[1] & 0x01,
  41242. degradationPriority: flags[2] << 8 | flags[3]
  41243. };
  41244. },
  41245. nalParse = function nalParse(avcStream) {
  41246. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  41247. result = [],
  41248. i,
  41249. length;
  41250. for (i = 0; i + 4 < avcStream.length; i += length) {
  41251. length = avcView.getUint32(i);
  41252. i += 4; // bail if this doesn't appear to be an H264 stream
  41253. if (length <= 0) {
  41254. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  41255. continue;
  41256. }
  41257. switch (avcStream[i] & 0x1F) {
  41258. case 0x01:
  41259. result.push('slice_layer_without_partitioning_rbsp');
  41260. break;
  41261. case 0x05:
  41262. result.push('slice_layer_without_partitioning_rbsp_idr');
  41263. break;
  41264. case 0x06:
  41265. result.push('sei_rbsp');
  41266. break;
  41267. case 0x07:
  41268. result.push('seq_parameter_set_rbsp');
  41269. break;
  41270. case 0x08:
  41271. result.push('pic_parameter_set_rbsp');
  41272. break;
  41273. case 0x09:
  41274. result.push('access_unit_delimiter_rbsp');
  41275. break;
  41276. default:
  41277. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  41278. break;
  41279. }
  41280. }
  41281. return result;
  41282. },
  41283. // registry of handlers for individual mp4 box types
  41284. parse$$1 = {
  41285. // codingname, not a first-class box type. stsd entries share the
  41286. // same format as real boxes so the parsing infrastructure can be
  41287. // shared
  41288. avc1: function avc1(data) {
  41289. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  41290. return {
  41291. dataReferenceIndex: view.getUint16(6),
  41292. width: view.getUint16(24),
  41293. height: view.getUint16(26),
  41294. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  41295. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  41296. frameCount: view.getUint16(40),
  41297. depth: view.getUint16(74),
  41298. config: inspectMp4(data.subarray(78, data.byteLength))
  41299. };
  41300. },
  41301. avcC: function avcC(data) {
  41302. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41303. result = {
  41304. configurationVersion: data[0],
  41305. avcProfileIndication: data[1],
  41306. profileCompatibility: data[2],
  41307. avcLevelIndication: data[3],
  41308. lengthSizeMinusOne: data[4] & 0x03,
  41309. sps: [],
  41310. pps: []
  41311. },
  41312. numOfSequenceParameterSets = data[5] & 0x1f,
  41313. numOfPictureParameterSets,
  41314. nalSize,
  41315. offset,
  41316. i; // iterate past any SPSs
  41317. offset = 6;
  41318. for (i = 0; i < numOfSequenceParameterSets; i++) {
  41319. nalSize = view.getUint16(offset);
  41320. offset += 2;
  41321. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  41322. offset += nalSize;
  41323. } // iterate past any PPSs
  41324. numOfPictureParameterSets = data[offset];
  41325. offset++;
  41326. for (i = 0; i < numOfPictureParameterSets; i++) {
  41327. nalSize = view.getUint16(offset);
  41328. offset += 2;
  41329. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  41330. offset += nalSize;
  41331. }
  41332. return result;
  41333. },
  41334. btrt: function btrt(data) {
  41335. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  41336. return {
  41337. bufferSizeDB: view.getUint32(0),
  41338. maxBitrate: view.getUint32(4),
  41339. avgBitrate: view.getUint32(8)
  41340. };
  41341. },
  41342. esds: function esds(data) {
  41343. return {
  41344. version: data[0],
  41345. flags: new Uint8Array(data.subarray(1, 4)),
  41346. esId: data[6] << 8 | data[7],
  41347. streamPriority: data[8] & 0x1f,
  41348. decoderConfig: {
  41349. objectProfileIndication: data[11],
  41350. streamType: data[12] >>> 2 & 0x3f,
  41351. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  41352. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  41353. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  41354. decoderConfigDescriptor: {
  41355. tag: data[24],
  41356. length: data[25],
  41357. audioObjectType: data[26] >>> 3 & 0x1f,
  41358. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  41359. channelConfiguration: data[27] >>> 3 & 0x0f
  41360. }
  41361. }
  41362. };
  41363. },
  41364. ftyp: function ftyp(data) {
  41365. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41366. result = {
  41367. majorBrand: parseType$2(data.subarray(0, 4)),
  41368. minorVersion: view.getUint32(4),
  41369. compatibleBrands: []
  41370. },
  41371. i = 8;
  41372. while (i < data.byteLength) {
  41373. result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
  41374. i += 4;
  41375. }
  41376. return result;
  41377. },
  41378. dinf: function dinf(data) {
  41379. return {
  41380. boxes: inspectMp4(data)
  41381. };
  41382. },
  41383. dref: function dref(data) {
  41384. return {
  41385. version: data[0],
  41386. flags: new Uint8Array(data.subarray(1, 4)),
  41387. dataReferences: inspectMp4(data.subarray(8))
  41388. };
  41389. },
  41390. hdlr: function hdlr(data) {
  41391. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41392. result = {
  41393. version: view.getUint8(0),
  41394. flags: new Uint8Array(data.subarray(1, 4)),
  41395. handlerType: parseType$2(data.subarray(8, 12)),
  41396. name: ''
  41397. },
  41398. i = 8; // parse out the name field
  41399. for (i = 24; i < data.byteLength; i++) {
  41400. if (data[i] === 0x00) {
  41401. // the name field is null-terminated
  41402. i++;
  41403. break;
  41404. }
  41405. result.name += String.fromCharCode(data[i]);
  41406. } // decode UTF-8 to javascript's internal representation
  41407. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  41408. result.name = decodeURIComponent(escape(result.name));
  41409. return result;
  41410. },
  41411. mdat: function mdat(data) {
  41412. return {
  41413. byteLength: data.byteLength,
  41414. nals: nalParse(data)
  41415. };
  41416. },
  41417. mdhd: function mdhd(data) {
  41418. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41419. i = 4,
  41420. language,
  41421. result = {
  41422. version: view.getUint8(0),
  41423. flags: new Uint8Array(data.subarray(1, 4)),
  41424. language: ''
  41425. };
  41426. if (result.version === 1) {
  41427. i += 4;
  41428. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  41429. i += 8;
  41430. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  41431. i += 4;
  41432. result.timescale = view.getUint32(i);
  41433. i += 8;
  41434. result.duration = view.getUint32(i); // truncating top 4 bytes
  41435. } else {
  41436. result.creationTime = parseMp4Date(view.getUint32(i));
  41437. i += 4;
  41438. result.modificationTime = parseMp4Date(view.getUint32(i));
  41439. i += 4;
  41440. result.timescale = view.getUint32(i);
  41441. i += 4;
  41442. result.duration = view.getUint32(i);
  41443. }
  41444. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  41445. // each field is the packed difference between its ASCII value and 0x60
  41446. language = view.getUint16(i);
  41447. result.language += String.fromCharCode((language >> 10) + 0x60);
  41448. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  41449. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  41450. return result;
  41451. },
  41452. mdia: function mdia(data) {
  41453. return {
  41454. boxes: inspectMp4(data)
  41455. };
  41456. },
  41457. mfhd: function mfhd(data) {
  41458. return {
  41459. version: data[0],
  41460. flags: new Uint8Array(data.subarray(1, 4)),
  41461. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  41462. };
  41463. },
  41464. minf: function minf(data) {
  41465. return {
  41466. boxes: inspectMp4(data)
  41467. };
  41468. },
  41469. // codingname, not a first-class box type. stsd entries share the
  41470. // same format as real boxes so the parsing infrastructure can be
  41471. // shared
  41472. mp4a: function mp4a(data) {
  41473. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41474. result = {
  41475. // 6 bytes reserved
  41476. dataReferenceIndex: view.getUint16(6),
  41477. // 4 + 4 bytes reserved
  41478. channelcount: view.getUint16(16),
  41479. samplesize: view.getUint16(18),
  41480. // 2 bytes pre_defined
  41481. // 2 bytes reserved
  41482. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  41483. }; // if there are more bytes to process, assume this is an ISO/IEC
  41484. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  41485. if (data.byteLength > 28) {
  41486. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  41487. }
  41488. return result;
  41489. },
  41490. moof: function moof(data) {
  41491. return {
  41492. boxes: inspectMp4(data)
  41493. };
  41494. },
  41495. moov: function moov(data) {
  41496. return {
  41497. boxes: inspectMp4(data)
  41498. };
  41499. },
  41500. mvex: function mvex(data) {
  41501. return {
  41502. boxes: inspectMp4(data)
  41503. };
  41504. },
  41505. mvhd: function mvhd(data) {
  41506. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41507. i = 4,
  41508. result = {
  41509. version: view.getUint8(0),
  41510. flags: new Uint8Array(data.subarray(1, 4))
  41511. };
  41512. if (result.version === 1) {
  41513. i += 4;
  41514. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  41515. i += 8;
  41516. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  41517. i += 4;
  41518. result.timescale = view.getUint32(i);
  41519. i += 8;
  41520. result.duration = view.getUint32(i); // truncating top 4 bytes
  41521. } else {
  41522. result.creationTime = parseMp4Date(view.getUint32(i));
  41523. i += 4;
  41524. result.modificationTime = parseMp4Date(view.getUint32(i));
  41525. i += 4;
  41526. result.timescale = view.getUint32(i);
  41527. i += 4;
  41528. result.duration = view.getUint32(i);
  41529. }
  41530. i += 4; // convert fixed-point, base 16 back to a number
  41531. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  41532. i += 4;
  41533. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  41534. i += 2;
  41535. i += 2;
  41536. i += 2 * 4;
  41537. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  41538. i += 9 * 4;
  41539. i += 6 * 4;
  41540. result.nextTrackId = view.getUint32(i);
  41541. return result;
  41542. },
  41543. pdin: function pdin(data) {
  41544. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  41545. return {
  41546. version: view.getUint8(0),
  41547. flags: new Uint8Array(data.subarray(1, 4)),
  41548. rate: view.getUint32(4),
  41549. initialDelay: view.getUint32(8)
  41550. };
  41551. },
  41552. sdtp: function sdtp(data) {
  41553. var result = {
  41554. version: data[0],
  41555. flags: new Uint8Array(data.subarray(1, 4)),
  41556. samples: []
  41557. },
  41558. i;
  41559. for (i = 4; i < data.byteLength; i++) {
  41560. result.samples.push({
  41561. dependsOn: (data[i] & 0x30) >> 4,
  41562. isDependedOn: (data[i] & 0x0c) >> 2,
  41563. hasRedundancy: data[i] & 0x03
  41564. });
  41565. }
  41566. return result;
  41567. },
  41568. sidx: function sidx(data) {
  41569. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41570. result = {
  41571. version: data[0],
  41572. flags: new Uint8Array(data.subarray(1, 4)),
  41573. references: [],
  41574. referenceId: view.getUint32(4),
  41575. timescale: view.getUint32(8),
  41576. earliestPresentationTime: view.getUint32(12),
  41577. firstOffset: view.getUint32(16)
  41578. },
  41579. referenceCount = view.getUint16(22),
  41580. i;
  41581. for (i = 24; referenceCount; i += 12, referenceCount--) {
  41582. result.references.push({
  41583. referenceType: (data[i] & 0x80) >>> 7,
  41584. referencedSize: view.getUint32(i) & 0x7FFFFFFF,
  41585. subsegmentDuration: view.getUint32(i + 4),
  41586. startsWithSap: !!(data[i + 8] & 0x80),
  41587. sapType: (data[i + 8] & 0x70) >>> 4,
  41588. sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
  41589. });
  41590. }
  41591. return result;
  41592. },
  41593. smhd: function smhd(data) {
  41594. return {
  41595. version: data[0],
  41596. flags: new Uint8Array(data.subarray(1, 4)),
  41597. balance: data[4] + data[5] / 256
  41598. };
  41599. },
  41600. stbl: function stbl(data) {
  41601. return {
  41602. boxes: inspectMp4(data)
  41603. };
  41604. },
  41605. stco: function stco(data) {
  41606. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41607. result = {
  41608. version: data[0],
  41609. flags: new Uint8Array(data.subarray(1, 4)),
  41610. chunkOffsets: []
  41611. },
  41612. entryCount = view.getUint32(4),
  41613. i;
  41614. for (i = 8; entryCount; i += 4, entryCount--) {
  41615. result.chunkOffsets.push(view.getUint32(i));
  41616. }
  41617. return result;
  41618. },
  41619. stsc: function stsc(data) {
  41620. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41621. entryCount = view.getUint32(4),
  41622. result = {
  41623. version: data[0],
  41624. flags: new Uint8Array(data.subarray(1, 4)),
  41625. sampleToChunks: []
  41626. },
  41627. i;
  41628. for (i = 8; entryCount; i += 12, entryCount--) {
  41629. result.sampleToChunks.push({
  41630. firstChunk: view.getUint32(i),
  41631. samplesPerChunk: view.getUint32(i + 4),
  41632. sampleDescriptionIndex: view.getUint32(i + 8)
  41633. });
  41634. }
  41635. return result;
  41636. },
  41637. stsd: function stsd(data) {
  41638. return {
  41639. version: data[0],
  41640. flags: new Uint8Array(data.subarray(1, 4)),
  41641. sampleDescriptions: inspectMp4(data.subarray(8))
  41642. };
  41643. },
  41644. stsz: function stsz(data) {
  41645. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41646. result = {
  41647. version: data[0],
  41648. flags: new Uint8Array(data.subarray(1, 4)),
  41649. sampleSize: view.getUint32(4),
  41650. entries: []
  41651. },
  41652. i;
  41653. for (i = 12; i < data.byteLength; i += 4) {
  41654. result.entries.push(view.getUint32(i));
  41655. }
  41656. return result;
  41657. },
  41658. stts: function stts(data) {
  41659. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41660. result = {
  41661. version: data[0],
  41662. flags: new Uint8Array(data.subarray(1, 4)),
  41663. timeToSamples: []
  41664. },
  41665. entryCount = view.getUint32(4),
  41666. i;
  41667. for (i = 8; entryCount; i += 8, entryCount--) {
  41668. result.timeToSamples.push({
  41669. sampleCount: view.getUint32(i),
  41670. sampleDelta: view.getUint32(i + 4)
  41671. });
  41672. }
  41673. return result;
  41674. },
  41675. styp: function styp(data) {
  41676. return parse$$1.ftyp(data);
  41677. },
  41678. tfdt: function tfdt(data) {
  41679. var result = {
  41680. version: data[0],
  41681. flags: new Uint8Array(data.subarray(1, 4)),
  41682. baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  41683. };
  41684. if (result.version === 1) {
  41685. result.baseMediaDecodeTime *= Math.pow(2, 32);
  41686. result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
  41687. }
  41688. return result;
  41689. },
  41690. tfhd: function tfhd(data) {
  41691. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41692. result = {
  41693. version: data[0],
  41694. flags: new Uint8Array(data.subarray(1, 4)),
  41695. trackId: view.getUint32(4)
  41696. },
  41697. baseDataOffsetPresent = result.flags[2] & 0x01,
  41698. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  41699. defaultSampleDurationPresent = result.flags[2] & 0x08,
  41700. defaultSampleSizePresent = result.flags[2] & 0x10,
  41701. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  41702. durationIsEmpty = result.flags[0] & 0x010000,
  41703. defaultBaseIsMoof = result.flags[0] & 0x020000,
  41704. i;
  41705. i = 8;
  41706. if (baseDataOffsetPresent) {
  41707. i += 4; // truncate top 4 bytes
  41708. // FIXME: should we read the full 64 bits?
  41709. result.baseDataOffset = view.getUint32(12);
  41710. i += 4;
  41711. }
  41712. if (sampleDescriptionIndexPresent) {
  41713. result.sampleDescriptionIndex = view.getUint32(i);
  41714. i += 4;
  41715. }
  41716. if (defaultSampleDurationPresent) {
  41717. result.defaultSampleDuration = view.getUint32(i);
  41718. i += 4;
  41719. }
  41720. if (defaultSampleSizePresent) {
  41721. result.defaultSampleSize = view.getUint32(i);
  41722. i += 4;
  41723. }
  41724. if (defaultSampleFlagsPresent) {
  41725. result.defaultSampleFlags = view.getUint32(i);
  41726. }
  41727. if (durationIsEmpty) {
  41728. result.durationIsEmpty = true;
  41729. }
  41730. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  41731. result.baseDataOffsetIsMoof = true;
  41732. }
  41733. return result;
  41734. },
  41735. tkhd: function tkhd(data) {
  41736. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41737. i = 4,
  41738. result = {
  41739. version: view.getUint8(0),
  41740. flags: new Uint8Array(data.subarray(1, 4))
  41741. };
  41742. if (result.version === 1) {
  41743. i += 4;
  41744. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  41745. i += 8;
  41746. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  41747. i += 4;
  41748. result.trackId = view.getUint32(i);
  41749. i += 4;
  41750. i += 8;
  41751. result.duration = view.getUint32(i); // truncating top 4 bytes
  41752. } else {
  41753. result.creationTime = parseMp4Date(view.getUint32(i));
  41754. i += 4;
  41755. result.modificationTime = parseMp4Date(view.getUint32(i));
  41756. i += 4;
  41757. result.trackId = view.getUint32(i);
  41758. i += 4;
  41759. i += 4;
  41760. result.duration = view.getUint32(i);
  41761. }
  41762. i += 4;
  41763. i += 2 * 4;
  41764. result.layer = view.getUint16(i);
  41765. i += 2;
  41766. result.alternateGroup = view.getUint16(i);
  41767. i += 2; // convert fixed-point, base 16 back to a number
  41768. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  41769. i += 2;
  41770. i += 2;
  41771. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  41772. i += 9 * 4;
  41773. result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
  41774. i += 4;
  41775. result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
  41776. return result;
  41777. },
  41778. traf: function traf(data) {
  41779. return {
  41780. boxes: inspectMp4(data)
  41781. };
  41782. },
  41783. trak: function trak(data) {
  41784. return {
  41785. boxes: inspectMp4(data)
  41786. };
  41787. },
  41788. trex: function trex(data) {
  41789. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  41790. return {
  41791. version: data[0],
  41792. flags: new Uint8Array(data.subarray(1, 4)),
  41793. trackId: view.getUint32(4),
  41794. defaultSampleDescriptionIndex: view.getUint32(8),
  41795. defaultSampleDuration: view.getUint32(12),
  41796. defaultSampleSize: view.getUint32(16),
  41797. sampleDependsOn: data[20] & 0x03,
  41798. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  41799. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  41800. samplePaddingValue: (data[21] & 0x0e) >> 1,
  41801. sampleIsDifferenceSample: !!(data[21] & 0x01),
  41802. sampleDegradationPriority: view.getUint16(22)
  41803. };
  41804. },
  41805. trun: function trun(data) {
  41806. var result = {
  41807. version: data[0],
  41808. flags: new Uint8Array(data.subarray(1, 4)),
  41809. samples: []
  41810. },
  41811. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  41812. // Flag interpretation
  41813. dataOffsetPresent = result.flags[2] & 0x01,
  41814. // compare with 2nd byte of 0x1
  41815. firstSampleFlagsPresent = result.flags[2] & 0x04,
  41816. // compare with 2nd byte of 0x4
  41817. sampleDurationPresent = result.flags[1] & 0x01,
  41818. // compare with 2nd byte of 0x100
  41819. sampleSizePresent = result.flags[1] & 0x02,
  41820. // compare with 2nd byte of 0x200
  41821. sampleFlagsPresent = result.flags[1] & 0x04,
  41822. // compare with 2nd byte of 0x400
  41823. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  41824. // compare with 2nd byte of 0x800
  41825. sampleCount = view.getUint32(4),
  41826. offset = 8,
  41827. sample;
  41828. if (dataOffsetPresent) {
  41829. // 32 bit signed integer
  41830. result.dataOffset = view.getInt32(offset);
  41831. offset += 4;
  41832. } // Overrides the flags for the first sample only. The order of
  41833. // optional values will be: duration, size, compositionTimeOffset
  41834. if (firstSampleFlagsPresent && sampleCount) {
  41835. sample = {
  41836. flags: parseSampleFlags(data.subarray(offset, offset + 4))
  41837. };
  41838. offset += 4;
  41839. if (sampleDurationPresent) {
  41840. sample.duration = view.getUint32(offset);
  41841. offset += 4;
  41842. }
  41843. if (sampleSizePresent) {
  41844. sample.size = view.getUint32(offset);
  41845. offset += 4;
  41846. }
  41847. if (sampleCompositionTimeOffsetPresent) {
  41848. // Note: this should be a signed int if version is 1
  41849. sample.compositionTimeOffset = view.getUint32(offset);
  41850. offset += 4;
  41851. }
  41852. result.samples.push(sample);
  41853. sampleCount--;
  41854. }
  41855. while (sampleCount--) {
  41856. sample = {};
  41857. if (sampleDurationPresent) {
  41858. sample.duration = view.getUint32(offset);
  41859. offset += 4;
  41860. }
  41861. if (sampleSizePresent) {
  41862. sample.size = view.getUint32(offset);
  41863. offset += 4;
  41864. }
  41865. if (sampleFlagsPresent) {
  41866. sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
  41867. offset += 4;
  41868. }
  41869. if (sampleCompositionTimeOffsetPresent) {
  41870. // Note: this should be a signed int if version is 1
  41871. sample.compositionTimeOffset = view.getUint32(offset);
  41872. offset += 4;
  41873. }
  41874. result.samples.push(sample);
  41875. }
  41876. return result;
  41877. },
  41878. 'url ': function url(data) {
  41879. return {
  41880. version: data[0],
  41881. flags: new Uint8Array(data.subarray(1, 4))
  41882. };
  41883. },
  41884. vmhd: function vmhd(data) {
  41885. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  41886. return {
  41887. version: data[0],
  41888. flags: new Uint8Array(data.subarray(1, 4)),
  41889. graphicsmode: view.getUint16(4),
  41890. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  41891. };
  41892. }
  41893. };
  41894. /**
  41895. * Return a javascript array of box objects parsed from an ISO base
  41896. * media file.
  41897. * @param data {Uint8Array} the binary data of the media to be inspected
  41898. * @return {array} a javascript array of potentially nested box objects
  41899. */
  41900. inspectMp4 = function inspectMp4(data) {
  41901. var i = 0,
  41902. result = [],
  41903. view,
  41904. size,
  41905. type,
  41906. end,
  41907. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  41908. var ab = new ArrayBuffer(data.length);
  41909. var v = new Uint8Array(ab);
  41910. for (var z = 0; z < data.length; ++z) {
  41911. v[z] = data[z];
  41912. }
  41913. view = new DataView(ab);
  41914. while (i < data.byteLength) {
  41915. // parse box data
  41916. size = view.getUint32(i);
  41917. type = parseType$2(data.subarray(i + 4, i + 8));
  41918. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  41919. box = (parse$$1[type] || function (data) {
  41920. return {
  41921. data: data
  41922. };
  41923. })(data.subarray(i + 8, end));
  41924. box.size = size;
  41925. box.type = type; // store this box and move to the next
  41926. result.push(box);
  41927. i = end;
  41928. }
  41929. return result;
  41930. };
  41931. /**
  41932. * Returns a textual representation of the javascript represtentation
  41933. * of an MP4 file. You can use it as an alternative to
  41934. * JSON.stringify() to compare inspected MP4s.
  41935. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  41936. * file
  41937. * @param depth {number} (optional) the number of ancestor boxes of
  41938. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  41939. * @return {string} a text representation of the parsed MP4
  41940. */
  41941. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  41942. var indent;
  41943. depth = depth || 0;
  41944. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  41945. return inspectedMp4.map(function (box, index) {
  41946. // list the box type first at the current indentation level
  41947. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  41948. Object.keys(box).filter(function (key) {
  41949. return key !== 'type' && key !== 'boxes'; // output all the box properties
  41950. }).map(function (key) {
  41951. var prefix = indent + ' ' + key + ': ',
  41952. value = box[key]; // print out raw bytes as hexademical
  41953. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  41954. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (_byte) {
  41955. return ' ' + ('00' + _byte.toString(16)).slice(-2);
  41956. }).join('').match(/.{1,24}/g);
  41957. if (!bytes) {
  41958. return prefix + '<>';
  41959. }
  41960. if (bytes.length === 1) {
  41961. return prefix + '<' + bytes.join('').slice(1) + '>';
  41962. }
  41963. return prefix + '<\n' + bytes.map(function (line) {
  41964. return indent + ' ' + line;
  41965. }).join('\n') + '\n' + indent + ' >';
  41966. } // stringify generic objects
  41967. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  41968. if (index === 0) {
  41969. return line;
  41970. }
  41971. return indent + ' ' + line;
  41972. }).join('\n');
  41973. }).join('\n') + ( // recursively textify the child boxes
  41974. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  41975. }).join('\n');
  41976. };
  41977. var mp4Inspector$$1 = {
  41978. inspect: inspectMp4,
  41979. textify: _textifyMp,
  41980. parseTfdt: parse$$1.tfdt,
  41981. parseHdlr: parse$$1.hdlr,
  41982. parseTfhd: parse$$1.tfhd,
  41983. parseTrun: parse$$1.trun,
  41984. parseSidx: parse$$1.sidx
  41985. };
  41986. var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
  41987. var CaptionStream$1 = captionStream.CaptionStream;
  41988. /**
  41989. * Maps an offset in the mdat to a sample based on the the size of the samples.
  41990. * Assumes that `parseSamples` has been called first.
  41991. *
  41992. * @param {Number} offset - The offset into the mdat
  41993. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  41994. * @return {?Object} The matching sample, or null if no match was found.
  41995. *
  41996. * @see ISO-BMFF-12/2015, Section 8.8.8
  41997. **/
  41998. var mapToSample = function mapToSample(offset, samples) {
  41999. var approximateOffset = offset;
  42000. for (var i = 0; i < samples.length; i++) {
  42001. var sample = samples[i];
  42002. if (approximateOffset < sample.size) {
  42003. return sample;
  42004. }
  42005. approximateOffset -= sample.size;
  42006. }
  42007. return null;
  42008. };
  42009. /**
  42010. * Finds SEI nal units contained in a Media Data Box.
  42011. * Assumes that `parseSamples` has been called first.
  42012. *
  42013. * @param {Uint8Array} avcStream - The bytes of the mdat
  42014. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  42015. * @param {Number} trackId - The trackId of this video track
  42016. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  42017. * The contents of the seiNal should match what is expected by
  42018. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  42019. *
  42020. * @see ISO-BMFF-12/2015, Section 8.1.1
  42021. * @see Rec. ITU-T H.264, 7.3.2.3.1
  42022. **/
  42023. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  42024. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  42025. result = [],
  42026. seiNal,
  42027. i,
  42028. length,
  42029. lastMatchedSample;
  42030. for (i = 0; i + 4 < avcStream.length; i += length) {
  42031. length = avcView.getUint32(i);
  42032. i += 4; // Bail if this doesn't appear to be an H264 stream
  42033. if (length <= 0) {
  42034. continue;
  42035. }
  42036. switch (avcStream[i] & 0x1F) {
  42037. case 0x06:
  42038. var data = avcStream.subarray(i + 1, i + 1 + length);
  42039. var matchingSample = mapToSample(i, samples);
  42040. seiNal = {
  42041. nalUnitType: 'sei_rbsp',
  42042. size: length,
  42043. data: data,
  42044. escapedRBSP: discardEmulationPreventionBytes$1(data),
  42045. trackId: trackId
  42046. };
  42047. if (matchingSample) {
  42048. seiNal.pts = matchingSample.pts;
  42049. seiNal.dts = matchingSample.dts;
  42050. lastMatchedSample = matchingSample;
  42051. } else {
  42052. // If a matching sample cannot be found, use the last
  42053. // sample's values as they should be as close as possible
  42054. seiNal.pts = lastMatchedSample.pts;
  42055. seiNal.dts = lastMatchedSample.dts;
  42056. }
  42057. result.push(seiNal);
  42058. break;
  42059. default:
  42060. break;
  42061. }
  42062. }
  42063. return result;
  42064. };
  42065. /**
  42066. * Parses sample information out of Track Run Boxes and calculates
  42067. * the absolute presentation and decode timestamps of each sample.
  42068. *
  42069. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  42070. * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
  42071. @see ISO-BMFF-12/2015, Section 8.8.12
  42072. * @param {Object} tfhd - The parsed Track Fragment Header
  42073. * @see inspect.parseTfhd
  42074. * @return {Object[]} the parsed samples
  42075. *
  42076. * @see ISO-BMFF-12/2015, Section 8.8.8
  42077. **/
  42078. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  42079. var currentDts = baseMediaDecodeTime;
  42080. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  42081. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  42082. var trackId = tfhd.trackId;
  42083. var allSamples = [];
  42084. truns.forEach(function (trun) {
  42085. // Note: We currently do not parse the sample table as well
  42086. // as the trun. It's possible some sources will require this.
  42087. // moov > trak > mdia > minf > stbl
  42088. var trackRun = mp4Inspector$$1.parseTrun(trun);
  42089. var samples = trackRun.samples;
  42090. samples.forEach(function (sample) {
  42091. if (sample.duration === undefined) {
  42092. sample.duration = defaultSampleDuration;
  42093. }
  42094. if (sample.size === undefined) {
  42095. sample.size = defaultSampleSize;
  42096. }
  42097. sample.trackId = trackId;
  42098. sample.dts = currentDts;
  42099. if (sample.compositionTimeOffset === undefined) {
  42100. sample.compositionTimeOffset = 0;
  42101. }
  42102. sample.pts = currentDts + sample.compositionTimeOffset;
  42103. currentDts += sample.duration;
  42104. });
  42105. allSamples = allSamples.concat(samples);
  42106. });
  42107. return allSamples;
  42108. };
  42109. /**
  42110. * Parses out caption nals from an FMP4 segment's video tracks.
  42111. *
  42112. * @param {Uint8Array} segment - The bytes of a single segment
  42113. * @param {Number} videoTrackId - The trackId of a video track in the segment
  42114. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  42115. * a list of seiNals found in that track
  42116. **/
  42117. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  42118. // To get the samples
  42119. var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
  42120. var mdats = probe.findBox(segment, ['mdat']);
  42121. var captionNals = {};
  42122. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  42123. mdats.forEach(function (mdat, index) {
  42124. var matchingTraf = trafs[index];
  42125. mdatTrafPairs.push({
  42126. mdat: mdat,
  42127. traf: matchingTraf
  42128. });
  42129. });
  42130. mdatTrafPairs.forEach(function (pair) {
  42131. var mdat = pair.mdat;
  42132. var traf = pair.traf;
  42133. var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
  42134. var headerInfo = mp4Inspector$$1.parseTfhd(tfhd[0]);
  42135. var trackId = headerInfo.trackId;
  42136. var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  42137. var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector$$1.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  42138. var truns = probe.findBox(traf, ['trun']);
  42139. var samples;
  42140. var seiNals; // Only parse video data for the chosen video track
  42141. if (videoTrackId === trackId && truns.length > 0) {
  42142. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  42143. seiNals = findSeiNals(mdat, samples, trackId);
  42144. if (!captionNals[trackId]) {
  42145. captionNals[trackId] = [];
  42146. }
  42147. captionNals[trackId] = captionNals[trackId].concat(seiNals);
  42148. }
  42149. });
  42150. return captionNals;
  42151. };
  42152. /**
  42153. * Parses out inband captions from an MP4 container and returns
  42154. * caption objects that can be used by WebVTT and the TextTrack API.
  42155. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  42156. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  42157. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  42158. *
  42159. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  42160. * @param {Number} trackId - The id of the video track to parse
  42161. * @param {Number} timescale - The timescale for the video track from the init segment
  42162. *
  42163. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  42164. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  42165. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  42166. * @return {String} parsedCaptions[].text - The visible content of the caption
  42167. **/
  42168. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  42169. var seiNals;
  42170. if (!trackId) {
  42171. return null;
  42172. }
  42173. seiNals = parseCaptionNals(segment, trackId);
  42174. return {
  42175. seiNals: seiNals[trackId],
  42176. timescale: timescale
  42177. };
  42178. };
  42179. /**
  42180. * Converts SEI NALUs into captions that can be used by video.js
  42181. **/
  42182. var CaptionParser$$1 = function CaptionParser$$1() {
  42183. var isInitialized = false;
  42184. var captionStream$$1; // Stores segments seen before trackId and timescale are set
  42185. var segmentCache; // Stores video track ID of the track being parsed
  42186. var trackId; // Stores the timescale of the track being parsed
  42187. var timescale; // Stores captions parsed so far
  42188. var parsedCaptions;
  42189. /**
  42190. * A method to indicate whether a CaptionParser has been initalized
  42191. * @returns {Boolean}
  42192. **/
  42193. this.isInitialized = function () {
  42194. return isInitialized;
  42195. };
  42196. /**
  42197. * Initializes the underlying CaptionStream, SEI NAL parsing
  42198. * and management, and caption collection
  42199. **/
  42200. this.init = function () {
  42201. captionStream$$1 = new CaptionStream$1();
  42202. isInitialized = true; // Collect dispatched captions
  42203. captionStream$$1.on('data', function (event) {
  42204. // Convert to seconds in the source's timescale
  42205. event.startTime = event.startPts / timescale;
  42206. event.endTime = event.endPts / timescale;
  42207. parsedCaptions.captions.push(event);
  42208. parsedCaptions.captionStreams[event.stream] = true;
  42209. });
  42210. };
  42211. /**
  42212. * Determines if a new video track will be selected
  42213. * or if the timescale changed
  42214. * @return {Boolean}
  42215. **/
  42216. this.isNewInit = function (videoTrackIds, timescales) {
  42217. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  42218. return false;
  42219. }
  42220. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  42221. };
  42222. /**
  42223. * Parses out SEI captions and interacts with underlying
  42224. * CaptionStream to return dispatched captions
  42225. *
  42226. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  42227. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  42228. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  42229. * @see parseEmbeddedCaptions
  42230. * @see m2ts/caption-stream.js
  42231. **/
  42232. this.parse = function (segment, videoTrackIds, timescales) {
  42233. var parsedData;
  42234. if (!this.isInitialized()) {
  42235. return null; // This is not likely to be a video segment
  42236. } else if (!videoTrackIds || !timescales) {
  42237. return null;
  42238. } else if (this.isNewInit(videoTrackIds, timescales)) {
  42239. // Use the first video track only as there is no
  42240. // mechanism to switch to other video tracks
  42241. trackId = videoTrackIds[0];
  42242. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  42243. // data until we have one
  42244. } else if (!trackId || !timescale) {
  42245. segmentCache.push(segment);
  42246. return null;
  42247. } // Now that a timescale and trackId is set, parse cached segments
  42248. while (segmentCache.length > 0) {
  42249. var cachedSegment = segmentCache.shift();
  42250. this.parse(cachedSegment, videoTrackIds, timescales);
  42251. }
  42252. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  42253. if (parsedData === null || !parsedData.seiNals) {
  42254. return null;
  42255. }
  42256. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  42257. this.flushStream();
  42258. return parsedCaptions;
  42259. };
  42260. /**
  42261. * Pushes SEI NALUs onto CaptionStream
  42262. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  42263. * Assumes that `parseCaptionNals` has been called first
  42264. * @see m2ts/caption-stream.js
  42265. **/
  42266. this.pushNals = function (nals) {
  42267. if (!this.isInitialized() || !nals || nals.length === 0) {
  42268. return null;
  42269. }
  42270. nals.forEach(function (nal) {
  42271. captionStream$$1.push(nal);
  42272. });
  42273. };
  42274. /**
  42275. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  42276. * @see m2ts/caption-stream.js
  42277. **/
  42278. this.flushStream = function () {
  42279. if (!this.isInitialized()) {
  42280. return null;
  42281. }
  42282. captionStream$$1.flush();
  42283. };
  42284. /**
  42285. * Reset caption buckets for new data
  42286. **/
  42287. this.clearParsedCaptions = function () {
  42288. parsedCaptions.captions = [];
  42289. parsedCaptions.captionStreams = {};
  42290. };
  42291. /**
  42292. * Resets underlying CaptionStream
  42293. * @see m2ts/caption-stream.js
  42294. **/
  42295. this.resetCaptionStream = function () {
  42296. if (!this.isInitialized()) {
  42297. return null;
  42298. }
  42299. captionStream$$1.reset();
  42300. };
  42301. /**
  42302. * Convenience method to clear all captions flushed from the
  42303. * CaptionStream and still being parsed
  42304. * @see m2ts/caption-stream.js
  42305. **/
  42306. this.clearAllCaptions = function () {
  42307. this.clearParsedCaptions();
  42308. this.resetCaptionStream();
  42309. };
  42310. /**
  42311. * Reset caption parser
  42312. **/
  42313. this.reset = function () {
  42314. segmentCache = [];
  42315. trackId = null;
  42316. timescale = null;
  42317. if (!parsedCaptions) {
  42318. parsedCaptions = {
  42319. captions: [],
  42320. // CC1, CC2, CC3, CC4
  42321. captionStreams: {}
  42322. };
  42323. } else {
  42324. this.clearParsedCaptions();
  42325. }
  42326. this.resetCaptionStream();
  42327. };
  42328. this.reset();
  42329. };
  42330. var captionParser = CaptionParser$$1;
  42331. /**
  42332. * mux.js
  42333. *
  42334. * Copyright (c) Brightcove
  42335. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  42336. */
  42337. var mp4 = {
  42338. generator: mp4Generator,
  42339. probe: probe,
  42340. Transmuxer: transmuxer.Transmuxer,
  42341. AudioSegmentStream: transmuxer.AudioSegmentStream,
  42342. VideoSegmentStream: transmuxer.VideoSegmentStream,
  42343. CaptionParser: captionParser
  42344. };
  42345. var classCallCheck = function classCallCheck(instance, Constructor) {
  42346. if (!(instance instanceof Constructor)) {
  42347. throw new TypeError("Cannot call a class as a function");
  42348. }
  42349. };
  42350. var createClass = function () {
  42351. function defineProperties(target, props) {
  42352. for (var i = 0; i < props.length; i++) {
  42353. var descriptor = props[i];
  42354. descriptor.enumerable = descriptor.enumerable || false;
  42355. descriptor.configurable = true;
  42356. if ("value" in descriptor) descriptor.writable = true;
  42357. Object.defineProperty(target, descriptor.key, descriptor);
  42358. }
  42359. }
  42360. return function (Constructor, protoProps, staticProps) {
  42361. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  42362. if (staticProps) defineProperties(Constructor, staticProps);
  42363. return Constructor;
  42364. };
  42365. }();
  42366. /**
  42367. * @file transmuxer-worker.js
  42368. */
  42369. /**
  42370. * Re-emits transmuxer events by converting them into messages to the
  42371. * world outside the worker.
  42372. *
  42373. * @param {Object} transmuxer the transmuxer to wire events on
  42374. * @private
  42375. */
  42376. var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) {
  42377. transmuxer.on('data', function (segment) {
  42378. // transfer ownership of the underlying ArrayBuffer
  42379. // instead of doing a copy to save memory
  42380. // ArrayBuffers are transferable but generic TypedArrays are not
  42381. // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
  42382. var initArray = segment.initSegment;
  42383. segment.initSegment = {
  42384. data: initArray.buffer,
  42385. byteOffset: initArray.byteOffset,
  42386. byteLength: initArray.byteLength
  42387. };
  42388. var typedArray = segment.data;
  42389. segment.data = typedArray.buffer;
  42390. self.postMessage({
  42391. action: 'data',
  42392. segment: segment,
  42393. byteOffset: typedArray.byteOffset,
  42394. byteLength: typedArray.byteLength
  42395. }, [segment.data]);
  42396. });
  42397. if (transmuxer.captionStream) {
  42398. transmuxer.captionStream.on('data', function (caption) {
  42399. self.postMessage({
  42400. action: 'caption',
  42401. data: caption
  42402. });
  42403. });
  42404. }
  42405. transmuxer.on('done', function (data) {
  42406. self.postMessage({
  42407. action: 'done'
  42408. });
  42409. });
  42410. transmuxer.on('gopInfo', function (gopInfo) {
  42411. self.postMessage({
  42412. action: 'gopInfo',
  42413. gopInfo: gopInfo
  42414. });
  42415. });
  42416. transmuxer.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) {
  42417. self.postMessage({
  42418. action: 'videoSegmentTimingInfo',
  42419. videoSegmentTimingInfo: videoSegmentTimingInfo
  42420. });
  42421. });
  42422. };
  42423. /**
  42424. * All incoming messages route through this hash. If no function exists
  42425. * to handle an incoming message, then we ignore the message.
  42426. *
  42427. * @class MessageHandlers
  42428. * @param {Object} options the options to initialize with
  42429. */
  42430. var MessageHandlers = function () {
  42431. function MessageHandlers(self, options) {
  42432. classCallCheck(this, MessageHandlers);
  42433. this.options = options || {};
  42434. this.self = self;
  42435. this.init();
  42436. }
  42437. /**
  42438. * initialize our web worker and wire all the events.
  42439. */
  42440. createClass(MessageHandlers, [{
  42441. key: 'init',
  42442. value: function init() {
  42443. if (this.transmuxer) {
  42444. this.transmuxer.dispose();
  42445. }
  42446. this.transmuxer = new mp4.Transmuxer(this.options);
  42447. wireTransmuxerEvents(this.self, this.transmuxer);
  42448. }
  42449. /**
  42450. * Adds data (a ts segment) to the start of the transmuxer pipeline for
  42451. * processing.
  42452. *
  42453. * @param {ArrayBuffer} data data to push into the muxer
  42454. */
  42455. }, {
  42456. key: 'push',
  42457. value: function push(data) {
  42458. // Cast array buffer to correct type for transmuxer
  42459. var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
  42460. this.transmuxer.push(segment);
  42461. }
  42462. /**
  42463. * Recreate the transmuxer so that the next segment added via `push`
  42464. * start with a fresh transmuxer.
  42465. */
  42466. }, {
  42467. key: 'reset',
  42468. value: function reset() {
  42469. this.init();
  42470. }
  42471. /**
  42472. * Set the value that will be used as the `baseMediaDecodeTime` time for the
  42473. * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
  42474. * set relative to the first based on the PTS values.
  42475. *
  42476. * @param {Object} data used to set the timestamp offset in the muxer
  42477. */
  42478. }, {
  42479. key: 'setTimestampOffset',
  42480. value: function setTimestampOffset(data) {
  42481. var timestampOffset = data.timestampOffset || 0;
  42482. this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
  42483. }
  42484. }, {
  42485. key: 'setAudioAppendStart',
  42486. value: function setAudioAppendStart(data) {
  42487. this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
  42488. }
  42489. /**
  42490. * Forces the pipeline to finish processing the last segment and emit it's
  42491. * results.
  42492. *
  42493. * @param {Object} data event data, not really used
  42494. */
  42495. }, {
  42496. key: 'flush',
  42497. value: function flush(data) {
  42498. this.transmuxer.flush();
  42499. }
  42500. }, {
  42501. key: 'resetCaptions',
  42502. value: function resetCaptions() {
  42503. this.transmuxer.resetCaptions();
  42504. }
  42505. }, {
  42506. key: 'alignGopsWith',
  42507. value: function alignGopsWith(data) {
  42508. this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
  42509. }
  42510. }]);
  42511. return MessageHandlers;
  42512. }();
  42513. /**
  42514. * Our web wroker interface so that things can talk to mux.js
  42515. * that will be running in a web worker. the scope is passed to this by
  42516. * webworkify.
  42517. *
  42518. * @param {Object} self the scope for the web worker
  42519. */
  42520. var TransmuxerWorker = function TransmuxerWorker(self) {
  42521. self.onmessage = function (event) {
  42522. if (event.data.action === 'init' && event.data.options) {
  42523. this.messageHandlers = new MessageHandlers(self, event.data.options);
  42524. return;
  42525. }
  42526. if (!this.messageHandlers) {
  42527. this.messageHandlers = new MessageHandlers(self);
  42528. }
  42529. if (event.data && event.data.action && event.data.action !== 'init') {
  42530. if (this.messageHandlers[event.data.action]) {
  42531. this.messageHandlers[event.data.action](event.data);
  42532. }
  42533. }
  42534. };
  42535. };
  42536. var transmuxerWorker = new TransmuxerWorker(self);
  42537. return transmuxerWorker;
  42538. }();
  42539. });
  42540. /**
  42541. * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
  42542. * codec strings, or translating codec strings into objects that can be examined.
  42543. */
  42544. // Default codec parameters if none were provided for video and/or audio
  42545. var defaultCodecs = {
  42546. videoCodec: 'avc1',
  42547. videoObjectTypeIndicator: '.4d400d',
  42548. // AAC-LC
  42549. audioProfile: '2'
  42550. };
  42551. /**
  42552. * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
  42553. * `avc1.<hhhhhh>`
  42554. *
  42555. * @param {Array} codecs an array of codec strings to fix
  42556. * @return {Array} the translated codec array
  42557. * @private
  42558. */
  42559. var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
  42560. return codecs.map(function (codec) {
  42561. return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
  42562. var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
  42563. var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
  42564. return 'avc1.' + profileHex + '00' + avcLevelHex;
  42565. });
  42566. });
  42567. };
  42568. /**
  42569. * Parses a codec string to retrieve the number of codecs specified,
  42570. * the video codec and object type indicator, and the audio profile.
  42571. */
  42572. var parseCodecs = function parseCodecs() {
  42573. var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  42574. var result = {
  42575. codecCount: 0
  42576. };
  42577. var parsed = void 0;
  42578. result.codecCount = codecs.split(',').length;
  42579. result.codecCount = result.codecCount || 2; // parse the video codec
  42580. parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
  42581. if (parsed) {
  42582. result.videoCodec = parsed[2];
  42583. result.videoObjectTypeIndicator = parsed[3];
  42584. } // parse the last field of the audio codec
  42585. result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
  42586. result.audioProfile = result.audioProfile && result.audioProfile[2];
  42587. return result;
  42588. };
  42589. /**
  42590. * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
  42591. * standard `avc1.<hhhhhh>`.
  42592. *
  42593. * @param codecString {String} the codec string
  42594. * @return {String} the codec string with old apple-style codecs replaced
  42595. *
  42596. * @private
  42597. */
  42598. var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
  42599. return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
  42600. return translateLegacyCodecs([match])[0];
  42601. });
  42602. };
  42603. /**
  42604. * Build a media mime-type string from a set of parameters
  42605. * @param {String} type either 'audio' or 'video'
  42606. * @param {String} container either 'mp2t' or 'mp4'
  42607. * @param {Array} codecs an array of codec strings to add
  42608. * @return {String} a valid media mime-type
  42609. */
  42610. var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
  42611. // The codecs array is filtered so that falsey values are
  42612. // dropped and don't cause Array#join to create spurious
  42613. // commas
  42614. return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
  42615. return !!c;
  42616. }).join(', ') + '"';
  42617. };
  42618. /**
  42619. * Returns the type container based on information in the playlist
  42620. * @param {Playlist} media the current media playlist
  42621. * @return {String} a valid media container type
  42622. */
  42623. var getContainerType = function getContainerType(media) {
  42624. // An initialization segment means the media playlist is an iframe
  42625. // playlist or is using the mp4 container. We don't currently
  42626. // support iframe playlists, so assume this is signalling mp4
  42627. // fragments.
  42628. if (media.segments && media.segments.length && media.segments[0].map) {
  42629. return 'mp4';
  42630. }
  42631. return 'mp2t';
  42632. };
  42633. /**
  42634. * Returns a set of codec strings parsed from the playlist or the default
  42635. * codec strings if no codecs were specified in the playlist
  42636. * @param {Playlist} media the current media playlist
  42637. * @return {Object} an object with the video and audio codecs
  42638. */
  42639. var getCodecs = function getCodecs(media) {
  42640. // if the codecs were explicitly specified, use them instead of the
  42641. // defaults
  42642. var mediaAttributes = media.attributes || {};
  42643. if (mediaAttributes.CODECS) {
  42644. return parseCodecs(mediaAttributes.CODECS);
  42645. }
  42646. return defaultCodecs;
  42647. };
  42648. var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
  42649. if (!master.mediaGroups.AUDIO || !audioGroupId) {
  42650. return null;
  42651. }
  42652. var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
  42653. if (!audioGroup) {
  42654. return null;
  42655. }
  42656. for (var name in audioGroup) {
  42657. var audioType = audioGroup[name];
  42658. if (audioType["default"] && audioType.playlists) {
  42659. // codec should be the same for all playlists within the audio type
  42660. return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
  42661. }
  42662. }
  42663. return null;
  42664. };
  42665. /**
  42666. * Calculates the MIME type strings for a working configuration of
  42667. * SourceBuffers to play variant streams in a master playlist. If
  42668. * there is no possible working configuration, an empty array will be
  42669. * returned.
  42670. *
  42671. * @param master {Object} the m3u8 object for the master playlist
  42672. * @param media {Object} the m3u8 object for the variant playlist
  42673. * @return {Array} the MIME type strings. If the array has more than
  42674. * one entry, the first element should be applied to the video
  42675. * SourceBuffer and the second to the audio SourceBuffer.
  42676. *
  42677. * @private
  42678. */
  42679. var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
  42680. var containerType = getContainerType(media);
  42681. var codecInfo = getCodecs(media);
  42682. var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video)
  42683. var isMuxed = true;
  42684. var isMaat = false;
  42685. if (!media) {
  42686. // Not enough information
  42687. return [];
  42688. }
  42689. if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
  42690. var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario
  42691. if (audioGroup) {
  42692. isMaat = true; // Start with the everything demuxed then...
  42693. isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
  42694. for (var groupId in audioGroup) {
  42695. // either a uri is present (if the case of HLS and an external playlist), or
  42696. // playlists is present (in the case of DASH where we don't have external audio
  42697. // playlists)
  42698. if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
  42699. isMuxed = true;
  42700. break;
  42701. }
  42702. }
  42703. }
  42704. } // HLS with multiple-audio tracks must always get an audio codec.
  42705. // Put another way, there is no way to have a video-only multiple-audio HLS!
  42706. if (isMaat && !codecInfo.audioProfile) {
  42707. if (!isMuxed) {
  42708. // It is possible for codecs to be specified on the audio media group playlist but
  42709. // not on the rendition playlist. This is mostly the case for DASH, where audio and
  42710. // video are always separate (and separately specified).
  42711. codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
  42712. }
  42713. if (!codecInfo.audioProfile) {
  42714. 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)');
  42715. codecInfo.audioProfile = defaultCodecs.audioProfile;
  42716. }
  42717. } // Generate the final codec strings from the codec object generated above
  42718. var codecStrings = {};
  42719. if (codecInfo.videoCodec) {
  42720. codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
  42721. }
  42722. if (codecInfo.audioProfile) {
  42723. codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
  42724. } // Finally, make and return an array with proper mime-types depending on
  42725. // the configuration
  42726. var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
  42727. var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
  42728. var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
  42729. if (isMaat) {
  42730. if (!isMuxed && codecStrings.video) {
  42731. return [justVideo, justAudio];
  42732. }
  42733. if (!isMuxed && !codecStrings.video) {
  42734. // There is no muxed content and no video codec string, so this is an audio only
  42735. // stream with alternate audio.
  42736. return [justAudio, justAudio];
  42737. } // There exists the possiblity that this will return a `video/container`
  42738. // mime-type for the first entry in the array even when there is only audio.
  42739. // This doesn't appear to be a problem and simplifies the code.
  42740. return [bothVideoAudio, justAudio];
  42741. } // If there is no video codec at all, always just return a single
  42742. // audio/<container> mime-type
  42743. if (!codecStrings.video) {
  42744. return [justAudio];
  42745. } // When not using separate audio media groups, audio and video is
  42746. // *always* muxed
  42747. return [bothVideoAudio];
  42748. };
  42749. /**
  42750. * Parse a content type header into a type and parameters
  42751. * object
  42752. *
  42753. * @param {String} type the content type header
  42754. * @return {Object} the parsed content-type
  42755. * @private
  42756. */
  42757. var parseContentType = function parseContentType(type) {
  42758. var object = {
  42759. type: '',
  42760. parameters: {}
  42761. };
  42762. var parameters = type.trim().split(';'); // first parameter should always be content-type
  42763. object.type = parameters.shift().trim();
  42764. parameters.forEach(function (parameter) {
  42765. var pair = parameter.trim().split('=');
  42766. if (pair.length > 1) {
  42767. var name = pair[0].replace(/"/g, '').trim();
  42768. var value = pair[1].replace(/"/g, '').trim();
  42769. object.parameters[name] = value;
  42770. }
  42771. });
  42772. return object;
  42773. };
  42774. /**
  42775. * Check if a codec string refers to an audio codec.
  42776. *
  42777. * @param {String} codec codec string to check
  42778. * @return {Boolean} if this is an audio codec
  42779. * @private
  42780. */
  42781. var isAudioCodec = function isAudioCodec(codec) {
  42782. return /mp4a\.\d+.\d+/i.test(codec);
  42783. };
  42784. /**
  42785. * Check if a codec string refers to a video codec.
  42786. *
  42787. * @param {String} codec codec string to check
  42788. * @return {Boolean} if this is a video codec
  42789. * @private
  42790. */
  42791. var isVideoCodec = function isVideoCodec(codec) {
  42792. return /avc1\.[\da-f]+/i.test(codec);
  42793. };
  42794. /**
  42795. * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
  42796. * front of current time.
  42797. *
  42798. * @param {Array} buffer
  42799. * The current buffer of gop information
  42800. * @param {Number} currentTime
  42801. * The current time
  42802. * @param {Double} mapping
  42803. * Offset to map display time to stream presentation time
  42804. * @return {Array}
  42805. * List of gops considered safe to append over
  42806. */
  42807. var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
  42808. if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
  42809. return [];
  42810. } // pts value for current time + 3 seconds to give a bit more wiggle room
  42811. var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000);
  42812. var i = void 0;
  42813. for (i = 0; i < buffer.length; i++) {
  42814. if (buffer[i].pts > currentTimePts) {
  42815. break;
  42816. }
  42817. }
  42818. return buffer.slice(i);
  42819. };
  42820. /**
  42821. * Appends gop information (timing and byteLength) received by the transmuxer for the
  42822. * gops appended in the last call to appendBuffer
  42823. *
  42824. * @param {Array} buffer
  42825. * The current buffer of gop information
  42826. * @param {Array} gops
  42827. * List of new gop information
  42828. * @param {boolean} replace
  42829. * If true, replace the buffer with the new gop information. If false, append the
  42830. * new gop information to the buffer in the right location of time.
  42831. * @return {Array}
  42832. * Updated list of gop information
  42833. */
  42834. var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
  42835. if (!gops.length) {
  42836. return buffer;
  42837. }
  42838. if (replace) {
  42839. // If we are in safe append mode, then completely overwrite the gop buffer
  42840. // with the most recent appeneded data. This will make sure that when appending
  42841. // future segments, we only try to align with gops that are both ahead of current
  42842. // time and in the last segment appended.
  42843. return gops.slice();
  42844. }
  42845. var start = gops[0].pts;
  42846. var i = 0;
  42847. for (i; i < buffer.length; i++) {
  42848. if (buffer[i].pts >= start) {
  42849. break;
  42850. }
  42851. }
  42852. return buffer.slice(0, i).concat(gops);
  42853. };
  42854. /**
  42855. * Removes gop information in buffer that overlaps with provided start and end
  42856. *
  42857. * @param {Array} buffer
  42858. * The current buffer of gop information
  42859. * @param {Double} start
  42860. * position to start the remove at
  42861. * @param {Double} end
  42862. * position to end the remove at
  42863. * @param {Double} mapping
  42864. * Offset to map display time to stream presentation time
  42865. */
  42866. var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
  42867. var startPts = Math.ceil((start - mapping) * 90000);
  42868. var endPts = Math.ceil((end - mapping) * 90000);
  42869. var updatedBuffer = buffer.slice();
  42870. var i = buffer.length;
  42871. while (i--) {
  42872. if (buffer[i].pts <= endPts) {
  42873. break;
  42874. }
  42875. }
  42876. if (i === -1) {
  42877. // no removal because end of remove range is before start of buffer
  42878. return updatedBuffer;
  42879. }
  42880. var j = i + 1;
  42881. while (j--) {
  42882. if (buffer[j].pts <= startPts) {
  42883. break;
  42884. }
  42885. } // clamp remove range start to 0 index
  42886. j = Math.max(j, 0);
  42887. updatedBuffer.splice(j, i - j + 1);
  42888. return updatedBuffer;
  42889. };
  42890. var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) {
  42891. var start = null;
  42892. var end = null;
  42893. var arity = 0;
  42894. var extents = [];
  42895. var ranges = []; // neither buffer has been created yet
  42896. if (!videoBuffer && !audioBuffer) {
  42897. return videojs$1.createTimeRange();
  42898. } // only one buffer is configured
  42899. if (!videoBuffer) {
  42900. return audioBuffer.buffered;
  42901. }
  42902. if (!audioBuffer) {
  42903. return videoBuffer.buffered;
  42904. } // both buffers are configured
  42905. if (audioDisabled) {
  42906. return videoBuffer.buffered;
  42907. } // both buffers are empty
  42908. if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) {
  42909. return videojs$1.createTimeRange();
  42910. } // Handle the case where we have both buffers and create an
  42911. // intersection of the two
  42912. var videoBuffered = videoBuffer.buffered;
  42913. var audioBuffered = audioBuffer.buffered;
  42914. var count = videoBuffered.length; // A) Gather up all start and end times
  42915. while (count--) {
  42916. extents.push({
  42917. time: videoBuffered.start(count),
  42918. type: 'start'
  42919. });
  42920. extents.push({
  42921. time: videoBuffered.end(count),
  42922. type: 'end'
  42923. });
  42924. }
  42925. count = audioBuffered.length;
  42926. while (count--) {
  42927. extents.push({
  42928. time: audioBuffered.start(count),
  42929. type: 'start'
  42930. });
  42931. extents.push({
  42932. time: audioBuffered.end(count),
  42933. type: 'end'
  42934. });
  42935. } // B) Sort them by time
  42936. extents.sort(function (a, b) {
  42937. return a.time - b.time;
  42938. }); // C) Go along one by one incrementing arity for start and decrementing
  42939. // arity for ends
  42940. for (count = 0; count < extents.length; count++) {
  42941. if (extents[count].type === 'start') {
  42942. arity++; // D) If arity is ever incremented to 2 we are entering an
  42943. // overlapping range
  42944. if (arity === 2) {
  42945. start = extents[count].time;
  42946. }
  42947. } else if (extents[count].type === 'end') {
  42948. arity--; // E) If arity is ever decremented to 1 we leaving an
  42949. // overlapping range
  42950. if (arity === 1) {
  42951. end = extents[count].time;
  42952. }
  42953. } // F) Record overlapping ranges
  42954. if (start !== null && end !== null) {
  42955. ranges.push([start, end]);
  42956. start = null;
  42957. end = null;
  42958. }
  42959. }
  42960. return videojs$1.createTimeRanges(ranges);
  42961. };
  42962. /**
  42963. * @file virtual-source-buffer.js
  42964. */
  42965. var ONE_SECOND_IN_TS$3 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the
  42966. // state of the `updating` property manually. We have to do this because
  42967. // Firefox changes `updating` to false long before triggering `updateend`
  42968. // events and that was causing strange problems in videojs-contrib-hls
  42969. var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
  42970. var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
  42971. var wrapper = Object.create(null);
  42972. wrapper.updating = false;
  42973. wrapper.realBuffer_ = sourceBuffer;
  42974. var _loop = function _loop(key) {
  42975. if (typeof sourceBuffer[key] === 'function') {
  42976. wrapper[key] = function () {
  42977. return sourceBuffer[key].apply(sourceBuffer, arguments);
  42978. };
  42979. } else if (typeof wrapper[key] === 'undefined') {
  42980. Object.defineProperty(wrapper, key, {
  42981. get: function get$$1() {
  42982. return sourceBuffer[key];
  42983. },
  42984. set: function set$$1(v) {
  42985. return sourceBuffer[key] = v;
  42986. }
  42987. });
  42988. }
  42989. };
  42990. for (var key in sourceBuffer) {
  42991. _loop(key);
  42992. }
  42993. return wrapper;
  42994. };
  42995. /**
  42996. * VirtualSourceBuffers exist so that we can transmux non native formats
  42997. * into a native format, but keep the same api as a native source buffer.
  42998. * It creates a transmuxer, that works in its own thread (a web worker) and
  42999. * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
  43000. * then send all of that data to the naive sourcebuffer so that it is
  43001. * indestinguishable from a natively supported format.
  43002. *
  43003. * @param {HtmlMediaSource} mediaSource the parent mediaSource
  43004. * @param {Array} codecs array of codecs that we will be dealing with
  43005. * @class VirtualSourceBuffer
  43006. * @extends video.js.EventTarget
  43007. */
  43008. var VirtualSourceBuffer = function (_videojs$EventTarget) {
  43009. inherits$1(VirtualSourceBuffer, _videojs$EventTarget);
  43010. function VirtualSourceBuffer(mediaSource, codecs) {
  43011. classCallCheck$1(this, VirtualSourceBuffer);
  43012. var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
  43013. _this.timestampOffset_ = 0;
  43014. _this.pendingBuffers_ = [];
  43015. _this.bufferUpdating_ = false;
  43016. _this.mediaSource_ = mediaSource;
  43017. _this.codecs_ = codecs;
  43018. _this.audioCodec_ = null;
  43019. _this.videoCodec_ = null;
  43020. _this.audioDisabled_ = false;
  43021. _this.appendAudioInitSegment_ = true;
  43022. _this.gopBuffer_ = [];
  43023. _this.timeMapping_ = 0;
  43024. _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
  43025. var options = {
  43026. remux: false,
  43027. alignGopsAtEnd: _this.safeAppend_
  43028. };
  43029. _this.codecs_.forEach(function (codec) {
  43030. if (isAudioCodec(codec)) {
  43031. _this.audioCodec_ = codec;
  43032. } else if (isVideoCodec(codec)) {
  43033. _this.videoCodec_ = codec;
  43034. }
  43035. }); // append muxed segments to their respective native buffers as
  43036. // soon as they are available
  43037. _this.transmuxer_ = new TransmuxWorker();
  43038. _this.transmuxer_.postMessage({
  43039. action: 'init',
  43040. options: options
  43041. });
  43042. _this.transmuxer_.onmessage = function (event) {
  43043. if (event.data.action === 'data') {
  43044. return _this.data_(event);
  43045. }
  43046. if (event.data.action === 'done') {
  43047. return _this.done_(event);
  43048. }
  43049. if (event.data.action === 'gopInfo') {
  43050. return _this.appendGopInfo_(event);
  43051. }
  43052. if (event.data.action === 'videoSegmentTimingInfo') {
  43053. return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo);
  43054. }
  43055. }; // this timestampOffset is a property with the side-effect of resetting
  43056. // baseMediaDecodeTime in the transmuxer on the setter
  43057. Object.defineProperty(_this, 'timestampOffset', {
  43058. get: function get$$1() {
  43059. return this.timestampOffset_;
  43060. },
  43061. set: function set$$1(val) {
  43062. if (typeof val === 'number' && val >= 0) {
  43063. this.timestampOffset_ = val;
  43064. this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline
  43065. this.gopBuffer_.length = 0;
  43066. this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to
  43067. // the desired timestampOffset for the next segment
  43068. this.transmuxer_.postMessage({
  43069. action: 'setTimestampOffset',
  43070. timestampOffset: val
  43071. });
  43072. }
  43073. }
  43074. }); // setting the append window affects both source buffers
  43075. Object.defineProperty(_this, 'appendWindowStart', {
  43076. get: function get$$1() {
  43077. return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
  43078. },
  43079. set: function set$$1(start) {
  43080. if (this.videoBuffer_) {
  43081. this.videoBuffer_.appendWindowStart = start;
  43082. }
  43083. if (this.audioBuffer_) {
  43084. this.audioBuffer_.appendWindowStart = start;
  43085. }
  43086. }
  43087. }); // this buffer is "updating" if either of its native buffers are
  43088. Object.defineProperty(_this, 'updating', {
  43089. get: function get$$1() {
  43090. return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
  43091. }
  43092. }); // the buffered property is the intersection of the buffered
  43093. // ranges of the native source buffers
  43094. Object.defineProperty(_this, 'buffered', {
  43095. get: function get$$1() {
  43096. return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_);
  43097. }
  43098. });
  43099. return _this;
  43100. }
  43101. /**
  43102. * When we get a data event from the transmuxer
  43103. * we call this function and handle the data that
  43104. * was sent to us
  43105. *
  43106. * @private
  43107. * @param {Event} event the data event from the transmuxer
  43108. */
  43109. createClass$1(VirtualSourceBuffer, [{
  43110. key: 'data_',
  43111. value: function data_(event) {
  43112. var segment = event.data.segment; // Cast ArrayBuffer to TypedArray
  43113. segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
  43114. segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
  43115. createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array
  43116. this.pendingBuffers_.push(segment);
  43117. return;
  43118. }
  43119. /**
  43120. * When we get a done event from the transmuxer
  43121. * we call this function and we process all
  43122. * of the pending data that we have been saving in the
  43123. * data_ function
  43124. *
  43125. * @private
  43126. * @param {Event} event the done event from the transmuxer
  43127. */
  43128. }, {
  43129. key: 'done_',
  43130. value: function done_(event) {
  43131. // Don't process and append data if the mediaSource is closed
  43132. if (this.mediaSource_.readyState === 'closed') {
  43133. this.pendingBuffers_.length = 0;
  43134. return;
  43135. } // All buffers should have been flushed from the muxer
  43136. // start processing anything we have received
  43137. this.processPendingSegments_();
  43138. return;
  43139. }
  43140. }, {
  43141. key: 'videoSegmentTimingInfo_',
  43142. value: function videoSegmentTimingInfo_(timingInfo) {
  43143. var timingInfoInSeconds = {
  43144. start: {
  43145. decode: timingInfo.start.dts / ONE_SECOND_IN_TS$3,
  43146. presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$3
  43147. },
  43148. end: {
  43149. decode: timingInfo.end.dts / ONE_SECOND_IN_TS$3,
  43150. presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$3
  43151. },
  43152. baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$3
  43153. };
  43154. if (timingInfo.prependedContentDuration) {
  43155. timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$3;
  43156. }
  43157. this.trigger({
  43158. type: 'videoSegmentTimingInfo',
  43159. videoSegmentTimingInfo: timingInfoInSeconds
  43160. });
  43161. }
  43162. /**
  43163. * Create our internal native audio/video source buffers and add
  43164. * event handlers to them with the following conditions:
  43165. * 1. they do not already exist on the mediaSource
  43166. * 2. this VSB has a codec for them
  43167. *
  43168. * @private
  43169. */
  43170. }, {
  43171. key: 'createRealSourceBuffers_',
  43172. value: function createRealSourceBuffers_() {
  43173. var _this2 = this;
  43174. var types = ['audio', 'video'];
  43175. types.forEach(function (type) {
  43176. // Don't create a SourceBuffer of this type if we don't have a
  43177. // codec for it
  43178. if (!_this2[type + 'Codec_']) {
  43179. return;
  43180. } // Do nothing if a SourceBuffer of this type already exists
  43181. if (_this2[type + 'Buffer_']) {
  43182. return;
  43183. }
  43184. var buffer = null; // If the mediasource already has a SourceBuffer for the codec
  43185. // use that
  43186. if (_this2.mediaSource_[type + 'Buffer_']) {
  43187. buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled
  43188. // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
  43189. // than createRealSourceBuffers_ is called to create the second
  43190. // VirtualSourceBuffer because that happens as a side-effect of
  43191. // videojs-contrib-hls starting the audioSegmentLoader. As a result,
  43192. // the audioBuffer is essentially "ownerless" and no one will toggle
  43193. // the `updating` state back to false once the `updateend` event is received
  43194. //
  43195. // Setting `updating` to false manually will work around this
  43196. // situation and allow work to continue
  43197. buffer.updating = false;
  43198. } else {
  43199. var codecProperty = type + 'Codec_';
  43200. var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
  43201. buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
  43202. _this2.mediaSource_[type + 'Buffer_'] = buffer;
  43203. }
  43204. _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer
  43205. ['update', 'updatestart', 'updateend'].forEach(function (event) {
  43206. buffer.addEventListener(event, function () {
  43207. // if audio is disabled
  43208. if (type === 'audio' && _this2.audioDisabled_) {
  43209. return;
  43210. }
  43211. if (event === 'updateend') {
  43212. _this2[type + 'Buffer_'].updating = false;
  43213. }
  43214. var shouldTrigger = types.every(function (t) {
  43215. // skip checking audio's updating status if audio
  43216. // is not enabled
  43217. if (t === 'audio' && _this2.audioDisabled_) {
  43218. return true;
  43219. } // if the other type is updating we don't trigger
  43220. if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
  43221. return false;
  43222. }
  43223. return true;
  43224. });
  43225. if (shouldTrigger) {
  43226. return _this2.trigger(event);
  43227. }
  43228. });
  43229. });
  43230. });
  43231. }
  43232. /**
  43233. * Emulate the native mediasource function, but our function will
  43234. * send all of the proposed segments to the transmuxer so that we
  43235. * can transmux them before we append them to our internal
  43236. * native source buffers in the correct format.
  43237. *
  43238. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
  43239. * @param {Uint8Array} segment the segment to append to the buffer
  43240. */
  43241. }, {
  43242. key: 'appendBuffer',
  43243. value: function appendBuffer(segment) {
  43244. // Start the internal "updating" state
  43245. this.bufferUpdating_ = true;
  43246. if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
  43247. var audioBuffered = this.audioBuffer_.buffered;
  43248. this.transmuxer_.postMessage({
  43249. action: 'setAudioAppendStart',
  43250. appendStart: audioBuffered.end(audioBuffered.length - 1)
  43251. });
  43252. }
  43253. if (this.videoBuffer_) {
  43254. this.transmuxer_.postMessage({
  43255. action: 'alignGopsWith',
  43256. gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_)
  43257. });
  43258. }
  43259. this.transmuxer_.postMessage({
  43260. action: 'push',
  43261. // Send the typed-array of data as an ArrayBuffer so that
  43262. // it can be sent as a "Transferable" and avoid the costly
  43263. // memory copy
  43264. data: segment.buffer,
  43265. // To recreate the original typed-array, we need information
  43266. // about what portion of the ArrayBuffer it was a view into
  43267. byteOffset: segment.byteOffset,
  43268. byteLength: segment.byteLength
  43269. }, [segment.buffer]);
  43270. this.transmuxer_.postMessage({
  43271. action: 'flush'
  43272. });
  43273. }
  43274. /**
  43275. * Appends gop information (timing and byteLength) received by the transmuxer for the
  43276. * gops appended in the last call to appendBuffer
  43277. *
  43278. * @param {Event} event
  43279. * The gopInfo event from the transmuxer
  43280. * @param {Array} event.data.gopInfo
  43281. * List of gop info to append
  43282. */
  43283. }, {
  43284. key: 'appendGopInfo_',
  43285. value: function appendGopInfo_(event) {
  43286. this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
  43287. }
  43288. /**
  43289. * Emulate the native mediasource function and remove parts
  43290. * of the buffer from any of our internal buffers that exist
  43291. *
  43292. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
  43293. * @param {Double} start position to start the remove at
  43294. * @param {Double} end position to end the remove at
  43295. */
  43296. }, {
  43297. key: 'remove',
  43298. value: function remove(start, end) {
  43299. if (this.videoBuffer_) {
  43300. this.videoBuffer_.updating = true;
  43301. this.videoBuffer_.remove(start, end);
  43302. this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
  43303. }
  43304. if (!this.audioDisabled_ && this.audioBuffer_) {
  43305. this.audioBuffer_.updating = true;
  43306. this.audioBuffer_.remove(start, end);
  43307. } // Remove Metadata Cues (id3)
  43308. removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions
  43309. if (this.inbandTextTracks_) {
  43310. for (var track in this.inbandTextTracks_) {
  43311. removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
  43312. }
  43313. }
  43314. }
  43315. /**
  43316. * Process any segments that the muxer has output
  43317. * Concatenate segments together based on type and append them into
  43318. * their respective sourceBuffers
  43319. *
  43320. * @private
  43321. */
  43322. }, {
  43323. key: 'processPendingSegments_',
  43324. value: function processPendingSegments_() {
  43325. var sortedSegments = {
  43326. video: {
  43327. segments: [],
  43328. bytes: 0
  43329. },
  43330. audio: {
  43331. segments: [],
  43332. bytes: 0
  43333. },
  43334. captions: [],
  43335. metadata: []
  43336. }; // Sort segments into separate video/audio arrays and
  43337. // keep track of their total byte lengths
  43338. sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
  43339. var type = segment.type;
  43340. var data = segment.data;
  43341. var initSegment = segment.initSegment;
  43342. segmentObj[type].segments.push(data);
  43343. segmentObj[type].bytes += data.byteLength;
  43344. segmentObj[type].initSegment = initSegment; // Gather any captions into a single array
  43345. if (segment.captions) {
  43346. segmentObj.captions = segmentObj.captions.concat(segment.captions);
  43347. }
  43348. if (segment.info) {
  43349. segmentObj[type].info = segment.info;
  43350. } // Gather any metadata into a single array
  43351. if (segment.metadata) {
  43352. segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
  43353. }
  43354. return segmentObj;
  43355. }, sortedSegments); // Create the real source buffers if they don't exist by now since we
  43356. // finally are sure what tracks are contained in the source
  43357. if (!this.videoBuffer_ && !this.audioBuffer_) {
  43358. // Remove any codecs that may have been specified by default but
  43359. // are no longer applicable now
  43360. if (sortedSegments.video.bytes === 0) {
  43361. this.videoCodec_ = null;
  43362. }
  43363. if (sortedSegments.audio.bytes === 0) {
  43364. this.audioCodec_ = null;
  43365. }
  43366. this.createRealSourceBuffers_();
  43367. }
  43368. if (sortedSegments.audio.info) {
  43369. this.mediaSource_.trigger({
  43370. type: 'audioinfo',
  43371. info: sortedSegments.audio.info
  43372. });
  43373. }
  43374. if (sortedSegments.video.info) {
  43375. this.mediaSource_.trigger({
  43376. type: 'videoinfo',
  43377. info: sortedSegments.video.info
  43378. });
  43379. }
  43380. if (this.appendAudioInitSegment_) {
  43381. if (!this.audioDisabled_ && this.audioBuffer_) {
  43382. sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
  43383. sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
  43384. }
  43385. this.appendAudioInitSegment_ = false;
  43386. }
  43387. var triggerUpdateend = false; // Merge multiple video and audio segments into one and append
  43388. if (this.videoBuffer_ && sortedSegments.video.bytes) {
  43389. sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
  43390. sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
  43391. this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
  43392. } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
  43393. // The transmuxer did not return any bytes of video, meaning it was all trimmed
  43394. // for gop alignment. Since we have a video buffer and audio is disabled, updateend
  43395. // will never be triggered by this source buffer, which will cause contrib-hls
  43396. // to be stuck forever waiting for updateend. If audio is not disabled, updateend
  43397. // will be triggered by the audio buffer, which will be sent upwards since the video
  43398. // buffer will not be in an updating state.
  43399. triggerUpdateend = true;
  43400. } // Add text-track data for all
  43401. addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
  43402. if (!this.audioDisabled_ && this.audioBuffer_) {
  43403. this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
  43404. }
  43405. this.pendingBuffers_.length = 0;
  43406. if (triggerUpdateend) {
  43407. this.trigger('updateend');
  43408. } // We are no longer in the internal "updating" state
  43409. this.bufferUpdating_ = false;
  43410. }
  43411. /**
  43412. * Combine all segments into a single Uint8Array and then append them
  43413. * to the destination buffer
  43414. *
  43415. * @param {Object} segmentObj
  43416. * @param {SourceBuffer} destinationBuffer native source buffer to append data to
  43417. * @private
  43418. */
  43419. }, {
  43420. key: 'concatAndAppendSegments_',
  43421. value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
  43422. var offset = 0;
  43423. var tempBuffer = void 0;
  43424. if (segmentObj.bytes) {
  43425. tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array
  43426. segmentObj.segments.forEach(function (segment) {
  43427. tempBuffer.set(segment, offset);
  43428. offset += segment.byteLength;
  43429. });
  43430. try {
  43431. destinationBuffer.updating = true;
  43432. destinationBuffer.appendBuffer(tempBuffer);
  43433. } catch (error) {
  43434. if (this.mediaSource_.player_) {
  43435. this.mediaSource_.player_.error({
  43436. code: -3,
  43437. type: 'APPEND_BUFFER_ERR',
  43438. message: error.message,
  43439. originalError: error
  43440. });
  43441. }
  43442. }
  43443. }
  43444. }
  43445. /**
  43446. * Emulate the native mediasource function. abort any soureBuffer
  43447. * actions and throw out any un-appended data.
  43448. *
  43449. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
  43450. */
  43451. }, {
  43452. key: 'abort',
  43453. value: function abort() {
  43454. if (this.videoBuffer_) {
  43455. this.videoBuffer_.abort();
  43456. }
  43457. if (!this.audioDisabled_ && this.audioBuffer_) {
  43458. this.audioBuffer_.abort();
  43459. }
  43460. if (this.transmuxer_) {
  43461. this.transmuxer_.postMessage({
  43462. action: 'reset'
  43463. });
  43464. }
  43465. this.pendingBuffers_.length = 0;
  43466. this.bufferUpdating_ = false;
  43467. }
  43468. }]);
  43469. return VirtualSourceBuffer;
  43470. }(videojs$1.EventTarget);
  43471. /**
  43472. * @file html-media-source.js
  43473. */
  43474. /**
  43475. * Our MediaSource implementation in HTML, mimics native
  43476. * MediaSource where/if possible.
  43477. *
  43478. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
  43479. * @class HtmlMediaSource
  43480. * @extends videojs.EventTarget
  43481. */
  43482. var HtmlMediaSource = function (_videojs$EventTarget) {
  43483. inherits$1(HtmlMediaSource, _videojs$EventTarget);
  43484. function HtmlMediaSource() {
  43485. classCallCheck$1(this, HtmlMediaSource);
  43486. var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
  43487. var property = void 0;
  43488. _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default
  43489. for (property in _this.nativeMediaSource_) {
  43490. if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
  43491. _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
  43492. }
  43493. } // emulate `duration` and `seekable` until seeking can be
  43494. // handled uniformly for live streams
  43495. // see https://github.com/w3c/media-source/issues/5
  43496. _this.duration_ = NaN;
  43497. Object.defineProperty(_this, 'duration', {
  43498. get: function get$$1() {
  43499. if (this.duration_ === Infinity) {
  43500. return this.duration_;
  43501. }
  43502. return this.nativeMediaSource_.duration;
  43503. },
  43504. set: function set$$1(duration) {
  43505. this.duration_ = duration;
  43506. if (duration !== Infinity) {
  43507. this.nativeMediaSource_.duration = duration;
  43508. return;
  43509. }
  43510. }
  43511. });
  43512. Object.defineProperty(_this, 'seekable', {
  43513. get: function get$$1() {
  43514. if (this.duration_ === Infinity) {
  43515. return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
  43516. }
  43517. return this.nativeMediaSource_.seekable;
  43518. }
  43519. });
  43520. Object.defineProperty(_this, 'readyState', {
  43521. get: function get$$1() {
  43522. return this.nativeMediaSource_.readyState;
  43523. }
  43524. });
  43525. Object.defineProperty(_this, 'activeSourceBuffers', {
  43526. get: function get$$1() {
  43527. return this.activeSourceBuffers_;
  43528. }
  43529. }); // the list of virtual and native SourceBuffers created by this
  43530. // MediaSource
  43531. _this.sourceBuffers = [];
  43532. _this.activeSourceBuffers_ = [];
  43533. /**
  43534. * update the list of active source buffers based upon various
  43535. * imformation from HLS and video.js
  43536. *
  43537. * @private
  43538. */
  43539. _this.updateActiveSourceBuffers_ = function () {
  43540. // Retain the reference but empty the array
  43541. _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will
  43542. // be disabled based on the codec of the source buffer
  43543. if (_this.sourceBuffers.length === 1) {
  43544. var sourceBuffer = _this.sourceBuffers[0];
  43545. sourceBuffer.appendAudioInitSegment_ = true;
  43546. sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
  43547. _this.activeSourceBuffers_.push(sourceBuffer);
  43548. return;
  43549. } // There are 2 source buffers, a combined (possibly video only) source buffer and
  43550. // and an audio only source buffer.
  43551. // By default, the audio in the combined virtual source buffer is enabled
  43552. // and the audio-only source buffer (if it exists) is disabled.
  43553. var disableCombined = false;
  43554. var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects?
  43555. // safari may do something like this
  43556. for (var i = 0; i < _this.player_.audioTracks().length; i++) {
  43557. var track = _this.player_.audioTracks()[i];
  43558. if (track.enabled && track.kind !== 'main') {
  43559. // The enabled track is an alternate audio track so disable the audio in
  43560. // the combined source buffer and enable the audio-only source buffer.
  43561. disableCombined = true;
  43562. disableAudioOnly = false;
  43563. break;
  43564. }
  43565. }
  43566. _this.sourceBuffers.forEach(function (sourceBuffer, index) {
  43567. /* eslinst-disable */
  43568. // TODO once codecs are required, we can switch to using the codecs to determine
  43569. // what stream is the video stream, rather than relying on videoTracks
  43570. /* eslinst-enable */
  43571. sourceBuffer.appendAudioInitSegment_ = true;
  43572. if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  43573. // combined
  43574. sourceBuffer.audioDisabled_ = disableCombined;
  43575. } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
  43576. // If the "combined" source buffer is video only, then we do not want
  43577. // disable the audio-only source buffer (this is mostly for demuxed
  43578. // audio and video hls)
  43579. sourceBuffer.audioDisabled_ = true;
  43580. disableAudioOnly = false;
  43581. } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  43582. // audio only
  43583. // In the case of audio only with alternate audio and disableAudioOnly is true
  43584. // this means we want to disable the audio on the alternate audio sourcebuffer
  43585. // but not the main "combined" source buffer. The "combined" source buffer is
  43586. // always at index 0, so this ensures audio won't be disabled in both source
  43587. // buffers.
  43588. sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
  43589. if (sourceBuffer.audioDisabled_) {
  43590. return;
  43591. }
  43592. }
  43593. _this.activeSourceBuffers_.push(sourceBuffer);
  43594. });
  43595. };
  43596. _this.onPlayerMediachange_ = function () {
  43597. _this.sourceBuffers.forEach(function (sourceBuffer) {
  43598. sourceBuffer.appendAudioInitSegment_ = true;
  43599. });
  43600. };
  43601. _this.onHlsReset_ = function () {
  43602. _this.sourceBuffers.forEach(function (sourceBuffer) {
  43603. if (sourceBuffer.transmuxer_) {
  43604. sourceBuffer.transmuxer_.postMessage({
  43605. action: 'resetCaptions'
  43606. });
  43607. }
  43608. });
  43609. };
  43610. _this.onHlsSegmentTimeMapping_ = function (event) {
  43611. _this.sourceBuffers.forEach(function (buffer) {
  43612. return buffer.timeMapping_ = event.mapping;
  43613. });
  43614. }; // Re-emit MediaSource events on the polyfill
  43615. ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
  43616. this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
  43617. }, _this); // capture the associated player when the MediaSource is
  43618. // successfully attached
  43619. _this.on('sourceopen', function (event) {
  43620. // Get the player this MediaSource is attached to
  43621. var video = document.querySelector('[src="' + _this.url_ + '"]');
  43622. if (!video) {
  43623. return;
  43624. }
  43625. _this.player_ = videojs$1(video.parentNode);
  43626. if (!_this.player_) {
  43627. return;
  43628. } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
  43629. // resets its state and flushes the buffer
  43630. _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
  43631. // SegmentLoader inspects an MTS segment and has an accurate stream to display
  43632. // time mapping
  43633. _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
  43634. if (_this.player_.audioTracks && _this.player_.audioTracks()) {
  43635. _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
  43636. _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
  43637. _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
  43638. }
  43639. _this.player_.on('mediachange', _this.onPlayerMediachange_);
  43640. });
  43641. _this.on('sourceended', function (event) {
  43642. var duration = durationOfVideo(_this.duration);
  43643. for (var i = 0; i < _this.sourceBuffers.length; i++) {
  43644. var sourcebuffer = _this.sourceBuffers[i];
  43645. var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
  43646. if (cues && cues.length) {
  43647. cues[cues.length - 1].endTime = duration;
  43648. }
  43649. }
  43650. }); // explicitly terminate any WebWorkers that were created
  43651. // by SourceHandlers
  43652. _this.on('sourceclose', function (event) {
  43653. this.sourceBuffers.forEach(function (sourceBuffer) {
  43654. if (sourceBuffer.transmuxer_) {
  43655. sourceBuffer.transmuxer_.terminate();
  43656. }
  43657. });
  43658. this.sourceBuffers.length = 0;
  43659. if (!this.player_) {
  43660. return;
  43661. }
  43662. if (this.player_.audioTracks && this.player_.audioTracks()) {
  43663. this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
  43664. this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
  43665. this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
  43666. } // We can only change this if the player hasn't been disposed of yet
  43667. // because `off` eventually tries to use the el_ property. If it has
  43668. // been disposed of, then don't worry about it because there are no
  43669. // event handlers left to unbind anyway
  43670. if (this.player_.el_) {
  43671. this.player_.off('mediachange', this.onPlayerMediachange_);
  43672. }
  43673. if (this.player_.tech_ && this.player_.tech_.el_) {
  43674. this.player_.tech_.off('hls-reset', this.onHlsReset_);
  43675. this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
  43676. }
  43677. });
  43678. return _this;
  43679. }
  43680. /**
  43681. * Add a range that that can now be seeked to.
  43682. *
  43683. * @param {Double} start where to start the addition
  43684. * @param {Double} end where to end the addition
  43685. * @private
  43686. */
  43687. createClass$1(HtmlMediaSource, [{
  43688. key: 'addSeekableRange_',
  43689. value: function addSeekableRange_(start, end) {
  43690. var error = void 0;
  43691. if (this.duration !== Infinity) {
  43692. error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
  43693. error.name = 'InvalidStateError';
  43694. error.code = 11;
  43695. throw error;
  43696. }
  43697. if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
  43698. this.nativeMediaSource_.duration = end;
  43699. }
  43700. }
  43701. /**
  43702. * Add a source buffer to the media source.
  43703. *
  43704. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
  43705. * @param {String} type the content-type of the content
  43706. * @return {Object} the created source buffer
  43707. */
  43708. }, {
  43709. key: 'addSourceBuffer',
  43710. value: function addSourceBuffer(type) {
  43711. var buffer = void 0;
  43712. var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport
  43713. // stream segments into fragmented MP4s
  43714. if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
  43715. var codecs = [];
  43716. if (parsedType.parameters && parsedType.parameters.codecs) {
  43717. codecs = parsedType.parameters.codecs.split(',');
  43718. codecs = translateLegacyCodecs(codecs);
  43719. codecs = codecs.filter(function (codec) {
  43720. return isAudioCodec(codec) || isVideoCodec(codec);
  43721. });
  43722. }
  43723. if (codecs.length === 0) {
  43724. codecs = ['avc1.4d400d', 'mp4a.40.2'];
  43725. }
  43726. buffer = new VirtualSourceBuffer(this, codecs);
  43727. if (this.sourceBuffers.length !== 0) {
  43728. // If another VirtualSourceBuffer already exists, then we are creating a
  43729. // SourceBuffer for an alternate audio track and therefore we know that
  43730. // the source has both an audio and video track.
  43731. // That means we should trigger the manual creation of the real
  43732. // SourceBuffers instead of waiting for the transmuxer to return data
  43733. this.sourceBuffers[0].createRealSourceBuffers_();
  43734. buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if
  43735. // a second source buffer is ever created
  43736. this.sourceBuffers[0].audioDisabled_ = true;
  43737. }
  43738. } else {
  43739. // delegate to the native implementation
  43740. buffer = this.nativeMediaSource_.addSourceBuffer(type);
  43741. }
  43742. this.sourceBuffers.push(buffer);
  43743. return buffer;
  43744. }
  43745. }]);
  43746. return HtmlMediaSource;
  43747. }(videojs$1.EventTarget);
  43748. /**
  43749. * @file videojs-contrib-media-sources.js
  43750. */
  43751. var urlCount = 0; // ------------
  43752. // Media Source
  43753. // ------------
  43754. // store references to the media sources so they can be connected
  43755. // to a video element (a swf object)
  43756. // TODO: can we store this somewhere local to this module?
  43757. videojs$1.mediaSources = {};
  43758. /**
  43759. * Provide a method for a swf object to notify JS that a
  43760. * media source is now open.
  43761. *
  43762. * @param {String} msObjectURL string referencing the MSE Object URL
  43763. * @param {String} swfId the swf id
  43764. */
  43765. var open = function open(msObjectURL, swfId) {
  43766. var mediaSource = videojs$1.mediaSources[msObjectURL];
  43767. if (mediaSource) {
  43768. mediaSource.trigger({
  43769. type: 'sourceopen',
  43770. swfId: swfId
  43771. });
  43772. } else {
  43773. throw new Error('Media Source not found (Video.js)');
  43774. }
  43775. };
  43776. /**
  43777. * Check to see if the native MediaSource object exists and supports
  43778. * an MP4 container with both H.264 video and AAC-LC audio.
  43779. *
  43780. * @return {Boolean} if native media sources are supported
  43781. */
  43782. var supportsNativeMediaSources = function supportsNativeMediaSources() {
  43783. return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
  43784. };
  43785. /**
  43786. * An emulation of the MediaSource API so that we can support
  43787. * native and non-native functionality. returns an instance of
  43788. * HtmlMediaSource.
  43789. *
  43790. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
  43791. */
  43792. var MediaSource = function MediaSource() {
  43793. this.MediaSource = {
  43794. open: open,
  43795. supportsNativeMediaSources: supportsNativeMediaSources
  43796. };
  43797. if (supportsNativeMediaSources()) {
  43798. return new HtmlMediaSource();
  43799. }
  43800. throw new Error('Cannot use create a virtual MediaSource for this video');
  43801. };
  43802. MediaSource.open = open;
  43803. MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
  43804. /**
  43805. * A wrapper around the native URL for our MSE object
  43806. * implementation, this object is exposed under videojs.URL
  43807. *
  43808. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
  43809. */
  43810. var URL$1 = {
  43811. /**
  43812. * A wrapper around the native createObjectURL for our objects.
  43813. * This function maps a native or emulated mediaSource to a blob
  43814. * url so that it can be loaded into video.js
  43815. *
  43816. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
  43817. * @param {MediaSource} object the object to create a blob url to
  43818. */
  43819. createObjectURL: function createObjectURL(object) {
  43820. var objectUrlPrefix = 'blob:vjs-media-source/';
  43821. var url = void 0; // use the native MediaSource to generate an object URL
  43822. if (object instanceof HtmlMediaSource) {
  43823. url = window$1.URL.createObjectURL(object.nativeMediaSource_);
  43824. object.url_ = url;
  43825. return url;
  43826. } // if the object isn't an emulated MediaSource, delegate to the
  43827. // native implementation
  43828. if (!(object instanceof HtmlMediaSource)) {
  43829. url = window$1.URL.createObjectURL(object);
  43830. object.url_ = url;
  43831. return url;
  43832. } // build a URL that can be used to map back to the emulated
  43833. // MediaSource
  43834. url = objectUrlPrefix + urlCount;
  43835. urlCount++; // setup the mapping back to object
  43836. videojs$1.mediaSources[url] = object;
  43837. return url;
  43838. }
  43839. };
  43840. videojs$1.MediaSource = MediaSource;
  43841. videojs$1.URL = URL$1;
  43842. var EventTarget$1$1 = videojs$1.EventTarget,
  43843. mergeOptions$2 = videojs$1.mergeOptions;
  43844. /**
  43845. * Returns a new master manifest that is the result of merging an updated master manifest
  43846. * into the original version.
  43847. *
  43848. * @param {Object} oldMaster
  43849. * The old parsed mpd object
  43850. * @param {Object} newMaster
  43851. * The updated parsed mpd object
  43852. * @return {Object}
  43853. * A new object representing the original master manifest with the updated media
  43854. * playlists merged in
  43855. */
  43856. var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
  43857. var noChanges = void 0;
  43858. var update = mergeOptions$2(oldMaster, {
  43859. // These are top level properties that can be updated
  43860. duration: newMaster.duration,
  43861. minimumUpdatePeriod: newMaster.minimumUpdatePeriod
  43862. }); // First update the playlists in playlist list
  43863. for (var i = 0; i < newMaster.playlists.length; i++) {
  43864. var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
  43865. if (playlistUpdate) {
  43866. update = playlistUpdate;
  43867. } else {
  43868. noChanges = true;
  43869. }
  43870. } // Then update media group playlists
  43871. forEachMediaGroup(newMaster, function (properties, type, group, label) {
  43872. if (properties.playlists && properties.playlists.length) {
  43873. var uri = properties.playlists[0].uri;
  43874. var _playlistUpdate = updateMaster(update, properties.playlists[0]);
  43875. if (_playlistUpdate) {
  43876. update = _playlistUpdate; // update the playlist reference within media groups
  43877. update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
  43878. noChanges = false;
  43879. }
  43880. }
  43881. });
  43882. if (noChanges) {
  43883. return null;
  43884. }
  43885. return update;
  43886. };
  43887. var generateSidxKey = function generateSidxKey(sidxInfo) {
  43888. // should be non-inclusive
  43889. var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1;
  43890. return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd;
  43891. }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
  43892. // If the SIDXs have maps, the two maps should match,
  43893. // both `a` and `b` missing SIDXs is considered matching.
  43894. // If `a` or `b` but not both have a map, they aren't matching.
  43895. var equivalentSidx = function equivalentSidx(a, b) {
  43896. var neitherMap = Boolean(!a.map && !b.map);
  43897. var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
  43898. return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
  43899. }; // exported for testing
  43900. var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) {
  43901. var newSidxMapping = {};
  43902. for (var uri in playlists) {
  43903. var playlist = playlists[uri];
  43904. var currentSidxInfo = playlist.sidx;
  43905. if (currentSidxInfo) {
  43906. var key = generateSidxKey(currentSidxInfo);
  43907. if (!oldSidxMapping[key]) {
  43908. break;
  43909. }
  43910. var savedSidxInfo = oldSidxMapping[key].sidxInfo;
  43911. if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
  43912. newSidxMapping[key] = oldSidxMapping[key];
  43913. }
  43914. }
  43915. }
  43916. return newSidxMapping;
  43917. };
  43918. /**
  43919. * A function that filters out changed items as they need to be requested separately.
  43920. *
  43921. * The method is exported for testing
  43922. *
  43923. * @param {Object} masterXml the mpd XML
  43924. * @param {string} srcUrl the mpd url
  43925. * @param {Date} clientOffset a time difference between server and client (passed through and not used)
  43926. * @param {Object} oldSidxMapping the SIDX to compare against
  43927. */
  43928. var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) {
  43929. // Don't pass current sidx mapping
  43930. var master = parse(masterXml, {
  43931. manifestUri: srcUrl,
  43932. clientOffset: clientOffset
  43933. });
  43934. var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping);
  43935. var mediaGroupSidx = videoSidx;
  43936. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  43937. if (properties.playlists && properties.playlists.length) {
  43938. var playlists = properties.playlists;
  43939. mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
  43940. }
  43941. });
  43942. return mediaGroupSidx;
  43943. }; // exported for testing
  43944. var requestSidx_ = function requestSidx_(sidxRange, playlist, xhr, options, finishProcessingFn) {
  43945. var sidxInfo = {
  43946. // resolve the segment URL relative to the playlist
  43947. uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri),
  43948. // resolvedUri: sidxRange.resolvedUri,
  43949. byterange: sidxRange.byterange,
  43950. // the segment's playlist
  43951. playlist: playlist
  43952. };
  43953. var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, {
  43954. responseType: 'arraybuffer',
  43955. headers: segmentXhrHeaders(sidxInfo)
  43956. });
  43957. return xhr(sidxRequestOptions, finishProcessingFn);
  43958. };
  43959. var DashPlaylistLoader = function (_EventTarget) {
  43960. inherits$1(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
  43961. // playlist loader setups from media groups will expect to be able to pass a playlist
  43962. // (since there aren't external URLs to media playlists with DASH)
  43963. function DashPlaylistLoader(srcUrlOrPlaylist, hls) {
  43964. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  43965. var masterPlaylistLoader = arguments[3];
  43966. classCallCheck$1(this, DashPlaylistLoader);
  43967. var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
  43968. var _options$withCredenti = options.withCredentials,
  43969. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  43970. _options$handleManife = options.handleManifestRedirects,
  43971. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  43972. _this.hls_ = hls;
  43973. _this.withCredentials = withCredentials;
  43974. _this.handleManifestRedirects = handleManifestRedirects;
  43975. if (!srcUrlOrPlaylist) {
  43976. throw new Error('A non-empty playlist URL or playlist is required');
  43977. } // event naming?
  43978. _this.on('minimumUpdatePeriod', function () {
  43979. _this.refreshXml_();
  43980. }); // live playlist staleness timeout
  43981. _this.on('mediaupdatetimeout', function () {
  43982. _this.refreshMedia_(_this.media().uri);
  43983. });
  43984. _this.state = 'HAVE_NOTHING';
  43985. _this.loadedPlaylists_ = {}; // initialize the loader state
  43986. // The masterPlaylistLoader will be created with a string
  43987. if (typeof srcUrlOrPlaylist === 'string') {
  43988. _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
  43989. // once multi-period is refactored
  43990. _this.sidxMapping_ = {};
  43991. return possibleConstructorReturn$1(_this);
  43992. }
  43993. _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist);
  43994. return _this;
  43995. }
  43996. createClass$1(DashPlaylistLoader, [{
  43997. key: 'setupChildLoader',
  43998. value: function setupChildLoader(masterPlaylistLoader, playlist) {
  43999. this.masterPlaylistLoader_ = masterPlaylistLoader;
  44000. this.childPlaylist_ = playlist;
  44001. }
  44002. }, {
  44003. key: 'dispose',
  44004. value: function dispose() {
  44005. this.stopRequest();
  44006. this.loadedPlaylists_ = {};
  44007. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  44008. window$1.clearTimeout(this.mediaRequest_);
  44009. window$1.clearTimeout(this.mediaUpdateTimeout);
  44010. }
  44011. }, {
  44012. key: 'hasPendingRequest',
  44013. value: function hasPendingRequest() {
  44014. return this.request || this.mediaRequest_;
  44015. }
  44016. }, {
  44017. key: 'stopRequest',
  44018. value: function stopRequest() {
  44019. if (this.request) {
  44020. var oldRequest = this.request;
  44021. this.request = null;
  44022. oldRequest.onreadystatechange = null;
  44023. oldRequest.abort();
  44024. }
  44025. }
  44026. }, {
  44027. key: 'sidxRequestFinished_',
  44028. value: function sidxRequestFinished_(playlist, master, startingState, doneFn) {
  44029. var _this2 = this;
  44030. return function (err, request) {
  44031. // disposed
  44032. if (!_this2.request) {
  44033. return;
  44034. } // pending request is cleared
  44035. _this2.request = null;
  44036. if (err) {
  44037. _this2.error = {
  44038. status: request.status,
  44039. message: 'DASH playlist request error at URL: ' + playlist.uri,
  44040. response: request.response,
  44041. // MEDIA_ERR_NETWORK
  44042. code: 2
  44043. };
  44044. if (startingState) {
  44045. _this2.state = startingState;
  44046. }
  44047. _this2.trigger('error');
  44048. return doneFn(master, null);
  44049. }
  44050. var bytes = new Uint8Array(request.response);
  44051. var sidx = mp4Inspector.parseSidx(bytes.subarray(8));
  44052. return doneFn(master, sidx);
  44053. };
  44054. }
  44055. }, {
  44056. key: 'media',
  44057. value: function media(playlist) {
  44058. var _this3 = this; // getter
  44059. if (!playlist) {
  44060. return this.media_;
  44061. } // setter
  44062. if (this.state === 'HAVE_NOTHING') {
  44063. throw new Error('Cannot switch media playlist from ' + this.state);
  44064. }
  44065. var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
  44066. if (typeof playlist === 'string') {
  44067. if (!this.master.playlists[playlist]) {
  44068. throw new Error('Unknown playlist URI: ' + playlist);
  44069. }
  44070. playlist = this.master.playlists[playlist];
  44071. }
  44072. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately
  44073. if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) {
  44074. this.state = 'HAVE_METADATA';
  44075. this.media_ = playlist; // trigger media change if the active media has been updated
  44076. if (mediaChange) {
  44077. this.trigger('mediachanging');
  44078. this.trigger('mediachange');
  44079. }
  44080. return;
  44081. } // switching to the active playlist is a no-op
  44082. if (!mediaChange) {
  44083. return;
  44084. } // switching from an already loaded playlist
  44085. if (this.media_) {
  44086. this.trigger('mediachanging');
  44087. }
  44088. if (!playlist.sidx) {
  44089. // Continue asynchronously if there is no sidx
  44090. // wait one tick to allow haveMaster to run first on a child loader
  44091. this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, {
  44092. startingState: startingState,
  44093. playlist: playlist
  44094. }), 0); // exit early and don't do sidx work
  44095. return;
  44096. } // we have sidx mappings
  44097. var oldMaster = void 0;
  44098. var sidxMapping = void 0; // sidxMapping is used when parsing the masterXml, so store
  44099. // it on the masterPlaylistLoader
  44100. if (this.masterPlaylistLoader_) {
  44101. oldMaster = this.masterPlaylistLoader_.master;
  44102. sidxMapping = this.masterPlaylistLoader_.sidxMapping_;
  44103. } else {
  44104. oldMaster = this.master;
  44105. sidxMapping = this.sidxMapping_;
  44106. }
  44107. var sidxKey = generateSidxKey(playlist.sidx);
  44108. sidxMapping[sidxKey] = {
  44109. sidxInfo: playlist.sidx
  44110. };
  44111. this.request = requestSidx_(playlist.sidx, playlist, this.hls_.xhr, {
  44112. handleManifestRedirects: this.handleManifestRedirects
  44113. }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) {
  44114. if (!newMaster || !sidx) {
  44115. throw new Error('failed to request sidx');
  44116. } // update loader's sidxMapping with parsed sidx box
  44117. sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata
  44118. _this3.haveMetadata({
  44119. startingState: startingState,
  44120. playlist: newMaster.playlists[playlist.uri]
  44121. });
  44122. }));
  44123. }
  44124. }, {
  44125. key: 'haveMetadata',
  44126. value: function haveMetadata(_ref) {
  44127. var startingState = _ref.startingState,
  44128. playlist = _ref.playlist;
  44129. this.state = 'HAVE_METADATA';
  44130. this.loadedPlaylists_[playlist.uri] = playlist;
  44131. this.mediaRequest_ = null; // This will trigger loadedplaylist
  44132. this.refreshMedia_(playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  44133. // to resolve setup of media groups
  44134. if (startingState === 'HAVE_MASTER') {
  44135. this.trigger('loadedmetadata');
  44136. } else {
  44137. // trigger media change if the active media has been updated
  44138. this.trigger('mediachange');
  44139. }
  44140. }
  44141. }, {
  44142. key: 'pause',
  44143. value: function pause() {
  44144. this.stopRequest();
  44145. window$1.clearTimeout(this.mediaUpdateTimeout);
  44146. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  44147. if (this.state === 'HAVE_NOTHING') {
  44148. // If we pause the loader before any data has been retrieved, its as if we never
  44149. // started, so reset to an unstarted state.
  44150. this.started = false;
  44151. }
  44152. }
  44153. }, {
  44154. key: 'load',
  44155. value: function load(isFinalRendition) {
  44156. var _this4 = this;
  44157. window$1.clearTimeout(this.mediaUpdateTimeout);
  44158. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  44159. var media = this.media();
  44160. if (isFinalRendition) {
  44161. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  44162. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  44163. return _this4.load();
  44164. }, delay);
  44165. return;
  44166. } // because the playlists are internal to the manifest, load should either load the
  44167. // main manifest, or do nothing but trigger an event
  44168. if (!this.started) {
  44169. this.start();
  44170. return;
  44171. }
  44172. this.trigger('loadedplaylist');
  44173. }
  44174. /**
  44175. * Parses the master xml string and updates playlist uri references
  44176. *
  44177. * @return {Object}
  44178. * The parsed mpd manifest object
  44179. */
  44180. }, {
  44181. key: 'parseMasterXml',
  44182. value: function parseMasterXml() {
  44183. var master = parse(this.masterXml_, {
  44184. manifestUri: this.srcUrl,
  44185. clientOffset: this.clientOffset_,
  44186. sidxMapping: this.sidxMapping_
  44187. });
  44188. master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH
  44189. // but reference playlists by their URI throughout the project
  44190. // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
  44191. for (var i = 0; i < master.playlists.length; i++) {
  44192. var phonyUri = 'placeholder-uri-' + i;
  44193. master.playlists[i].uri = phonyUri; // set up by URI references
  44194. master.playlists[phonyUri] = master.playlists[i];
  44195. } // set up phony URIs for the media group playlists since we won't have external
  44196. // URIs for DASH but reference playlists by their URI throughout the project
  44197. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  44198. if (properties.playlists && properties.playlists.length) {
  44199. var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
  44200. properties.playlists[0].uri = _phonyUri; // setup URI references
  44201. master.playlists[_phonyUri] = properties.playlists[0];
  44202. }
  44203. });
  44204. setupMediaPlaylists(master);
  44205. resolveMediaGroupUris(master);
  44206. return master;
  44207. }
  44208. }, {
  44209. key: 'start',
  44210. value: function start() {
  44211. var _this5 = this;
  44212. this.started = true; // We don't need to request the master manifest again
  44213. // Call this asynchronously to match the xhr request behavior below
  44214. if (this.masterPlaylistLoader_) {
  44215. this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0);
  44216. return;
  44217. } // request the specified URL
  44218. this.request = this.hls_.xhr({
  44219. uri: this.srcUrl,
  44220. withCredentials: this.withCredentials
  44221. }, function (error, req) {
  44222. // disposed
  44223. if (!_this5.request) {
  44224. return;
  44225. } // clear the loader's request reference
  44226. _this5.request = null;
  44227. if (error) {
  44228. _this5.error = {
  44229. status: req.status,
  44230. message: 'DASH playlist request error at URL: ' + _this5.srcUrl,
  44231. responseText: req.responseText,
  44232. // MEDIA_ERR_NETWORK
  44233. code: 2
  44234. };
  44235. if (_this5.state === 'HAVE_NOTHING') {
  44236. _this5.started = false;
  44237. }
  44238. return _this5.trigger('error');
  44239. }
  44240. _this5.masterXml_ = req.responseText;
  44241. if (req.responseHeaders && req.responseHeaders.date) {
  44242. _this5.masterLoaded_ = Date.parse(req.responseHeaders.date);
  44243. } else {
  44244. _this5.masterLoaded_ = Date.now();
  44245. }
  44246. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  44247. _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5));
  44248. });
  44249. }
  44250. /**
  44251. * Parses the master xml for UTCTiming node to sync the client clock to the server
  44252. * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
  44253. *
  44254. * @param {Function} done
  44255. * Function to call when clock sync has completed
  44256. */
  44257. }, {
  44258. key: 'syncClientServerClock_',
  44259. value: function syncClientServerClock_(done) {
  44260. var _this6 = this;
  44261. var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
  44262. // server clock
  44263. if (utcTiming === null) {
  44264. this.clientOffset_ = this.masterLoaded_ - Date.now();
  44265. return done();
  44266. }
  44267. if (utcTiming.method === 'DIRECT') {
  44268. this.clientOffset_ = utcTiming.value - Date.now();
  44269. return done();
  44270. }
  44271. this.request = this.hls_.xhr({
  44272. uri: resolveUrl$1(this.srcUrl, utcTiming.value),
  44273. method: utcTiming.method,
  44274. withCredentials: this.withCredentials
  44275. }, function (error, req) {
  44276. // disposed
  44277. if (!_this6.request) {
  44278. return;
  44279. }
  44280. if (error) {
  44281. // sync request failed, fall back to using date header from mpd
  44282. // TODO: log warning
  44283. _this6.clientOffset_ = _this6.masterLoaded_ - Date.now();
  44284. return done();
  44285. }
  44286. var serverTime = void 0;
  44287. if (utcTiming.method === 'HEAD') {
  44288. if (!req.responseHeaders || !req.responseHeaders.date) {
  44289. // expected date header not preset, fall back to using date header from mpd
  44290. // TODO: log warning
  44291. serverTime = _this6.masterLoaded_;
  44292. } else {
  44293. serverTime = Date.parse(req.responseHeaders.date);
  44294. }
  44295. } else {
  44296. serverTime = Date.parse(req.responseText);
  44297. }
  44298. _this6.clientOffset_ = serverTime - Date.now();
  44299. done();
  44300. });
  44301. }
  44302. }, {
  44303. key: 'haveMaster_',
  44304. value: function haveMaster_() {
  44305. this.state = 'HAVE_MASTER'; // clear media request
  44306. this.mediaRequest_ = null;
  44307. if (!this.masterPlaylistLoader_) {
  44308. this.master = this.parseMasterXml(); // We have the master playlist at this point, so
  44309. // trigger this to allow MasterPlaylistController
  44310. // to make an initial playlist selection
  44311. this.trigger('loadedplaylist');
  44312. } else if (!this.media_) {
  44313. // no media playlist was specifically selected so select
  44314. // the one the child playlist loader was created with
  44315. this.media(this.childPlaylist_);
  44316. }
  44317. }
  44318. /**
  44319. * Handler for after client/server clock synchronization has happened. Sets up
  44320. * xml refresh timer if specificed by the manifest.
  44321. */
  44322. }, {
  44323. key: 'onClientServerClockSync_',
  44324. value: function onClientServerClockSync_() {
  44325. var _this7 = this;
  44326. this.haveMaster_();
  44327. if (!this.hasPendingRequest() && !this.media_) {
  44328. this.media(this.master.playlists[0]);
  44329. } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
  44330. // be refreshed when this is the case. The inter-op guide says that when the
  44331. // minimumUpdatePeriod is 0, the manifest should outline all currently available
  44332. // segments, but future segments may require an update. I think a good solution
  44333. // would be to update the manifest at the same rate that the media playlists
  44334. // are "refreshed", i.e. every targetDuration.
  44335. if (this.master && this.master.minimumUpdatePeriod) {
  44336. this.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  44337. _this7.trigger('minimumUpdatePeriod');
  44338. }, this.master.minimumUpdatePeriod);
  44339. }
  44340. }
  44341. /**
  44342. * Sends request to refresh the master xml and updates the parsed master manifest
  44343. * TODO: Does the client offset need to be recalculated when the xml is refreshed?
  44344. */
  44345. }, {
  44346. key: 'refreshXml_',
  44347. value: function refreshXml_() {
  44348. var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when
  44349. // sidx is implemented
  44350. this.request = this.hls_.xhr({
  44351. uri: this.srcUrl,
  44352. withCredentials: this.withCredentials
  44353. }, function (error, req) {
  44354. // disposed
  44355. if (!_this8.request) {
  44356. return;
  44357. } // clear the loader's request reference
  44358. _this8.request = null;
  44359. if (error) {
  44360. _this8.error = {
  44361. status: req.status,
  44362. message: 'DASH playlist request error at URL: ' + _this8.srcUrl,
  44363. responseText: req.responseText,
  44364. // MEDIA_ERR_NETWORK
  44365. code: 2
  44366. };
  44367. if (_this8.state === 'HAVE_NOTHING') {
  44368. _this8.started = false;
  44369. }
  44370. return _this8.trigger('error');
  44371. }
  44372. _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping
  44373. _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_);
  44374. var master = _this8.parseMasterXml();
  44375. var updatedMaster = updateMaster$1(_this8.master, master);
  44376. if (updatedMaster) {
  44377. var sidxKey = generateSidxKey(_this8.media().sidx); // the sidx was updated, so the previous mapping was removed
  44378. if (!_this8.sidxMapping_[sidxKey]) {
  44379. var playlist = _this8.media();
  44380. _this8.request = requestSidx_(playlist.sidx, playlist, _this8.hls_.xhr, {
  44381. handleManifestRedirects: _this8.handleManifestRedirects
  44382. }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) {
  44383. if (!newMaster || !sidx) {
  44384. throw new Error('failed to request sidx on minimumUpdatePeriod');
  44385. } // update loader's sidxMapping with parsed sidx box
  44386. _this8.sidxMapping_[sidxKey].sidx = sidx;
  44387. _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  44388. _this8.trigger('minimumUpdatePeriod');
  44389. }, _this8.master.minimumUpdatePeriod); // TODO: do we need to reload the current playlist?
  44390. _this8.refreshMedia_(_this8.media().uri);
  44391. return;
  44392. }));
  44393. } else {
  44394. _this8.master = updatedMaster;
  44395. }
  44396. }
  44397. _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  44398. _this8.trigger('minimumUpdatePeriod');
  44399. }, _this8.master.minimumUpdatePeriod);
  44400. });
  44401. }
  44402. /**
  44403. * Refreshes the media playlist by re-parsing the master xml and updating playlist
  44404. * references. If this is an alternate loader, the updated parsed manifest is retrieved
  44405. * from the master loader.
  44406. */
  44407. }, {
  44408. key: 'refreshMedia_',
  44409. value: function refreshMedia_(mediaUri) {
  44410. var _this9 = this;
  44411. if (!mediaUri) {
  44412. throw new Error('refreshMedia_ must take a media uri');
  44413. }
  44414. var oldMaster = void 0;
  44415. var newMaster = void 0;
  44416. if (this.masterPlaylistLoader_) {
  44417. oldMaster = this.masterPlaylistLoader_.master;
  44418. newMaster = this.masterPlaylistLoader_.parseMasterXml();
  44419. } else {
  44420. oldMaster = this.master;
  44421. newMaster = this.parseMasterXml();
  44422. }
  44423. var updatedMaster = updateMaster$1(oldMaster, newMaster);
  44424. if (updatedMaster) {
  44425. if (this.masterPlaylistLoader_) {
  44426. this.masterPlaylistLoader_.master = updatedMaster;
  44427. } else {
  44428. this.master = updatedMaster;
  44429. }
  44430. this.media_ = updatedMaster.playlists[mediaUri];
  44431. } else {
  44432. this.media_ = newMaster.playlists[mediaUri];
  44433. this.trigger('playlistunchanged');
  44434. }
  44435. if (!this.media().endList) {
  44436. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  44437. _this9.trigger('mediaupdatetimeout');
  44438. }, refreshDelay(this.media(), !!updatedMaster));
  44439. }
  44440. this.trigger('loadedplaylist');
  44441. }
  44442. }]);
  44443. return DashPlaylistLoader;
  44444. }(EventTarget$1$1);
  44445. var logger = function logger(source) {
  44446. if (videojs$1.log.debug) {
  44447. return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
  44448. }
  44449. return function () {};
  44450. };
  44451. function noop$1() {}
  44452. /**
  44453. * @file source-updater.js
  44454. */
  44455. /**
  44456. * A queue of callbacks to be serialized and applied when a
  44457. * MediaSource and its associated SourceBuffers are not in the
  44458. * updating state. It is used by the segment loader to update the
  44459. * underlying SourceBuffers when new data is loaded, for instance.
  44460. *
  44461. * @class SourceUpdater
  44462. * @param {MediaSource} mediaSource the MediaSource to create the
  44463. * SourceBuffer from
  44464. * @param {String} mimeType the desired MIME type of the underlying
  44465. * SourceBuffer
  44466. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
  44467. * added to the media source
  44468. */
  44469. var SourceUpdater = function () {
  44470. function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
  44471. classCallCheck$1(this, SourceUpdater);
  44472. this.callbacks_ = [];
  44473. this.pendingCallback_ = null;
  44474. this.timestampOffset_ = 0;
  44475. this.mediaSource = mediaSource;
  44476. this.processedAppend_ = false;
  44477. this.type_ = type;
  44478. this.mimeType_ = mimeType;
  44479. this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
  44480. if (mediaSource.readyState === 'closed') {
  44481. mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
  44482. } else {
  44483. this.createSourceBuffer_(mimeType, sourceBufferEmitter);
  44484. }
  44485. }
  44486. createClass$1(SourceUpdater, [{
  44487. key: 'createSourceBuffer_',
  44488. value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
  44489. var _this = this;
  44490. this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
  44491. this.logger_('created SourceBuffer');
  44492. if (sourceBufferEmitter) {
  44493. sourceBufferEmitter.trigger('sourcebufferadded');
  44494. if (this.mediaSource.sourceBuffers.length < 2) {
  44495. // There's another source buffer we must wait for before we can start updating
  44496. // our own (or else we can get into a bad state, i.e., appending video/audio data
  44497. // before the other video/audio source buffer is available and leading to a video
  44498. // or audio only buffer).
  44499. sourceBufferEmitter.on('sourcebufferadded', function () {
  44500. _this.start_();
  44501. });
  44502. return;
  44503. }
  44504. }
  44505. this.start_();
  44506. }
  44507. }, {
  44508. key: 'start_',
  44509. value: function start_() {
  44510. var _this2 = this;
  44511. this.started_ = true; // run completion handlers and process callbacks as updateend
  44512. // events fire
  44513. this.onUpdateendCallback_ = function () {
  44514. var pendingCallback = _this2.pendingCallback_;
  44515. _this2.pendingCallback_ = null;
  44516. _this2.sourceBuffer_.removing = false;
  44517. _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
  44518. if (pendingCallback) {
  44519. pendingCallback();
  44520. }
  44521. _this2.runCallback_();
  44522. };
  44523. this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
  44524. this.runCallback_();
  44525. }
  44526. /**
  44527. * Aborts the current segment and resets the segment parser.
  44528. *
  44529. * @param {Function} done function to call when done
  44530. * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
  44531. */
  44532. }, {
  44533. key: 'abort',
  44534. value: function abort(done) {
  44535. var _this3 = this;
  44536. if (this.processedAppend_) {
  44537. this.queueCallback_(function () {
  44538. _this3.sourceBuffer_.abort();
  44539. }, done);
  44540. }
  44541. }
  44542. /**
  44543. * Queue an update to append an ArrayBuffer.
  44544. *
  44545. * @param {ArrayBuffer} bytes
  44546. * @param {Function} done the function to call when done
  44547. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
  44548. */
  44549. }, {
  44550. key: 'appendBuffer',
  44551. value: function appendBuffer(config, done) {
  44552. var _this4 = this;
  44553. this.processedAppend_ = true;
  44554. this.queueCallback_(function () {
  44555. if (config.videoSegmentTimingInfoCallback) {
  44556. _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  44557. }
  44558. _this4.sourceBuffer_.appendBuffer(config.bytes);
  44559. }, function () {
  44560. if (config.videoSegmentTimingInfoCallback) {
  44561. _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  44562. }
  44563. done();
  44564. });
  44565. }
  44566. /**
  44567. * Indicates what TimeRanges are buffered in the managed SourceBuffer.
  44568. *
  44569. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
  44570. */
  44571. }, {
  44572. key: 'buffered',
  44573. value: function buffered() {
  44574. if (!this.sourceBuffer_) {
  44575. return videojs$1.createTimeRanges();
  44576. }
  44577. return this.sourceBuffer_.buffered;
  44578. }
  44579. /**
  44580. * Queue an update to remove a time range from the buffer.
  44581. *
  44582. * @param {Number} start where to start the removal
  44583. * @param {Number} end where to end the removal
  44584. * @param {Function} [done=noop] optional callback to be executed when the remove
  44585. * operation is complete
  44586. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
  44587. */
  44588. }, {
  44589. key: 'remove',
  44590. value: function remove(start, end) {
  44591. var _this5 = this;
  44592. var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1;
  44593. if (this.processedAppend_) {
  44594. this.queueCallback_(function () {
  44595. _this5.logger_('remove [' + start + ' => ' + end + ']');
  44596. _this5.sourceBuffer_.removing = true;
  44597. _this5.sourceBuffer_.remove(start, end);
  44598. }, done);
  44599. }
  44600. }
  44601. /**
  44602. * Whether the underlying sourceBuffer is updating or not
  44603. *
  44604. * @return {Boolean} the updating status of the SourceBuffer
  44605. */
  44606. }, {
  44607. key: 'updating',
  44608. value: function updating() {
  44609. // we are updating if the sourcebuffer is updating or
  44610. return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop
  44611. !!this.pendingCallback_ && this.pendingCallback_ !== noop$1;
  44612. }
  44613. /**
  44614. * Set/get the timestampoffset on the SourceBuffer
  44615. *
  44616. * @return {Number} the timestamp offset
  44617. */
  44618. }, {
  44619. key: 'timestampOffset',
  44620. value: function timestampOffset(offset) {
  44621. var _this6 = this;
  44622. if (typeof offset !== 'undefined') {
  44623. this.queueCallback_(function () {
  44624. _this6.sourceBuffer_.timestampOffset = offset;
  44625. _this6.runCallback_();
  44626. });
  44627. this.timestampOffset_ = offset;
  44628. }
  44629. return this.timestampOffset_;
  44630. }
  44631. /**
  44632. * Queue a callback to run
  44633. */
  44634. }, {
  44635. key: 'queueCallback_',
  44636. value: function queueCallback_(callback, done) {
  44637. this.callbacks_.push([callback.bind(this), done]);
  44638. this.runCallback_();
  44639. }
  44640. /**
  44641. * Run a queued callback
  44642. */
  44643. }, {
  44644. key: 'runCallback_',
  44645. value: function runCallback_() {
  44646. var callbacks = void 0;
  44647. if (!this.updating() && this.callbacks_.length && this.started_) {
  44648. callbacks = this.callbacks_.shift();
  44649. this.pendingCallback_ = callbacks[1];
  44650. callbacks[0]();
  44651. }
  44652. }
  44653. /**
  44654. * dispose of the source updater and the underlying sourceBuffer
  44655. */
  44656. }, {
  44657. key: 'dispose',
  44658. value: function dispose() {
  44659. var _this7 = this;
  44660. var disposeFn = function disposeFn() {
  44661. if (_this7.sourceBuffer_ && _this7.mediaSource.readyState === 'open') {
  44662. _this7.sourceBuffer_.abort();
  44663. }
  44664. _this7.sourceBuffer_.removeEventListener('updateend', disposeFn);
  44665. };
  44666. this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
  44667. if (this.sourceBuffer_.removing) {
  44668. this.sourceBuffer_.addEventListener('updateend', disposeFn);
  44669. } else {
  44670. disposeFn();
  44671. }
  44672. }
  44673. }]);
  44674. return SourceUpdater;
  44675. }();
  44676. var Config = {
  44677. GOAL_BUFFER_LENGTH: 30,
  44678. MAX_GOAL_BUFFER_LENGTH: 60,
  44679. GOAL_BUFFER_LENGTH_RATE: 1,
  44680. // 0.5 MB/s
  44681. INITIAL_BANDWIDTH: 4194304,
  44682. // A fudge factor to apply to advertised playlist bitrates to account for
  44683. // temporary flucations in client bandwidth
  44684. BANDWIDTH_VARIANCE: 1.2,
  44685. // How much of the buffer must be filled before we consider upswitching
  44686. BUFFER_LOW_WATER_LINE: 0,
  44687. MAX_BUFFER_LOW_WATER_LINE: 30,
  44688. BUFFER_LOW_WATER_LINE_RATE: 1
  44689. };
  44690. var REQUEST_ERRORS = {
  44691. FAILURE: 2,
  44692. TIMEOUT: -101,
  44693. ABORTED: -102
  44694. };
  44695. /**
  44696. * Abort all requests
  44697. *
  44698. * @param {Object} activeXhrs - an object that tracks all XHR requests
  44699. */
  44700. var abortAll = function abortAll(activeXhrs) {
  44701. activeXhrs.forEach(function (xhr) {
  44702. xhr.abort();
  44703. });
  44704. };
  44705. /**
  44706. * Gather important bandwidth stats once a request has completed
  44707. *
  44708. * @param {Object} request - the XHR request from which to gather stats
  44709. */
  44710. var getRequestStats = function getRequestStats(request) {
  44711. return {
  44712. bandwidth: request.bandwidth,
  44713. bytesReceived: request.bytesReceived || 0,
  44714. roundTripTime: request.roundTripTime || 0
  44715. };
  44716. };
  44717. /**
  44718. * If possible gather bandwidth stats as a request is in
  44719. * progress
  44720. *
  44721. * @param {Event} progressEvent - an event object from an XHR's progress event
  44722. */
  44723. var getProgressStats = function getProgressStats(progressEvent) {
  44724. var request = progressEvent.target;
  44725. var roundTripTime = Date.now() - request.requestTime;
  44726. var stats = {
  44727. bandwidth: Infinity,
  44728. bytesReceived: 0,
  44729. roundTripTime: roundTripTime || 0
  44730. };
  44731. stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
  44732. // because we should only use bandwidth stats on progress to determine when
  44733. // abort a request early due to insufficient bandwidth
  44734. stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
  44735. return stats;
  44736. };
  44737. /**
  44738. * Handle all error conditions in one place and return an object
  44739. * with all the information
  44740. *
  44741. * @param {Error|null} error - if non-null signals an error occured with the XHR
  44742. * @param {Object} request - the XHR request that possibly generated the error
  44743. */
  44744. var handleErrors = function handleErrors(error, request) {
  44745. if (request.timedout) {
  44746. return {
  44747. status: request.status,
  44748. message: 'HLS request timed-out at URL: ' + request.uri,
  44749. code: REQUEST_ERRORS.TIMEOUT,
  44750. xhr: request
  44751. };
  44752. }
  44753. if (request.aborted) {
  44754. return {
  44755. status: request.status,
  44756. message: 'HLS request aborted at URL: ' + request.uri,
  44757. code: REQUEST_ERRORS.ABORTED,
  44758. xhr: request
  44759. };
  44760. }
  44761. if (error) {
  44762. return {
  44763. status: request.status,
  44764. message: 'HLS request errored at URL: ' + request.uri,
  44765. code: REQUEST_ERRORS.FAILURE,
  44766. xhr: request
  44767. };
  44768. }
  44769. return null;
  44770. };
  44771. /**
  44772. * Handle responses for key data and convert the key data to the correct format
  44773. * for the decryption step later
  44774. *
  44775. * @param {Object} segment - a simplified copy of the segmentInfo object
  44776. * from SegmentLoader
  44777. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  44778. * this request
  44779. */
  44780. var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
  44781. return function (error, request) {
  44782. var response = request.response;
  44783. var errorObj = handleErrors(error, request);
  44784. if (errorObj) {
  44785. return finishProcessingFn(errorObj, segment);
  44786. }
  44787. if (response.byteLength !== 16) {
  44788. return finishProcessingFn({
  44789. status: request.status,
  44790. message: 'Invalid HLS key at URL: ' + request.uri,
  44791. code: REQUEST_ERRORS.FAILURE,
  44792. xhr: request
  44793. }, segment);
  44794. }
  44795. var view = new DataView(response);
  44796. segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
  44797. return finishProcessingFn(null, segment);
  44798. };
  44799. };
  44800. /**
  44801. * Handle init-segment responses
  44802. *
  44803. * @param {Object} segment - a simplified copy of the segmentInfo object
  44804. * from SegmentLoader
  44805. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  44806. * this request
  44807. */
  44808. var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) {
  44809. return function (error, request) {
  44810. var response = request.response;
  44811. var errorObj = handleErrors(error, request);
  44812. if (errorObj) {
  44813. return finishProcessingFn(errorObj, segment);
  44814. } // stop processing if received empty content
  44815. if (response.byteLength === 0) {
  44816. return finishProcessingFn({
  44817. status: request.status,
  44818. message: 'Empty HLS segment content at URL: ' + request.uri,
  44819. code: REQUEST_ERRORS.FAILURE,
  44820. xhr: request
  44821. }, segment);
  44822. }
  44823. segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet
  44824. if (captionParser && !captionParser.isInitialized()) {
  44825. captionParser.init();
  44826. }
  44827. segment.map.timescales = probe.timescale(segment.map.bytes);
  44828. segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes);
  44829. return finishProcessingFn(null, segment);
  44830. };
  44831. };
  44832. /**
  44833. * Response handler for segment-requests being sure to set the correct
  44834. * property depending on whether the segment is encryped or not
  44835. * Also records and keeps track of stats that are used for ABR purposes
  44836. *
  44837. * @param {Object} segment - a simplified copy of the segmentInfo object
  44838. * from SegmentLoader
  44839. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  44840. * this request
  44841. */
  44842. var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) {
  44843. return function (error, request) {
  44844. var response = request.response;
  44845. var errorObj = handleErrors(error, request);
  44846. var parsed = void 0;
  44847. if (errorObj) {
  44848. return finishProcessingFn(errorObj, segment);
  44849. } // stop processing if received empty content
  44850. if (response.byteLength === 0) {
  44851. return finishProcessingFn({
  44852. status: request.status,
  44853. message: 'Empty HLS segment content at URL: ' + request.uri,
  44854. code: REQUEST_ERRORS.FAILURE,
  44855. xhr: request
  44856. }, segment);
  44857. }
  44858. segment.stats = getRequestStats(request);
  44859. if (segment.key) {
  44860. segment.encryptedBytes = new Uint8Array(request.response);
  44861. } else {
  44862. segment.bytes = new Uint8Array(request.response);
  44863. } // This is likely an FMP4 and has the init segment.
  44864. // Run through the CaptionParser in case there are captions.
  44865. if (captionParser && segment.map && segment.map.bytes) {
  44866. // Initialize CaptionParser if it hasn't been yet
  44867. if (!captionParser.isInitialized()) {
  44868. captionParser.init();
  44869. }
  44870. parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales);
  44871. if (parsed && parsed.captions) {
  44872. segment.captionStreams = parsed.captionStreams;
  44873. segment.fmp4Captions = parsed.captions;
  44874. }
  44875. }
  44876. return finishProcessingFn(null, segment);
  44877. };
  44878. };
  44879. /**
  44880. * Decrypt the segment via the decryption web worker
  44881. *
  44882. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  44883. * @param {Object} segment - a simplified copy of the segmentInfo object
  44884. * from SegmentLoader
  44885. * @param {Function} doneFn - a callback that is executed after decryption has completed
  44886. */
  44887. var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
  44888. var decryptionHandler = function decryptionHandler(event) {
  44889. if (event.data.source === segment.requestId) {
  44890. decrypter.removeEventListener('message', decryptionHandler);
  44891. var decrypted = event.data.decrypted;
  44892. segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
  44893. return doneFn(null, segment);
  44894. }
  44895. };
  44896. decrypter.addEventListener('message', decryptionHandler);
  44897. var keyBytes = void 0;
  44898. if (segment.key.bytes.slice) {
  44899. keyBytes = segment.key.bytes.slice();
  44900. } else {
  44901. keyBytes = new Uint32Array(Array.prototype.slice.call(segment.key.bytes));
  44902. } // this is an encrypted segment
  44903. // incrementally decrypt the segment
  44904. decrypter.postMessage(createTransferableMessage({
  44905. source: segment.requestId,
  44906. encrypted: segment.encryptedBytes,
  44907. key: keyBytes,
  44908. iv: segment.key.iv
  44909. }), [segment.encryptedBytes.buffer, keyBytes.buffer]);
  44910. };
  44911. /**
  44912. * This function waits for all XHRs to finish (with either success or failure)
  44913. * before continueing processing via it's callback. The function gathers errors
  44914. * from each request into a single errors array so that the error status for
  44915. * each request can be examined later.
  44916. *
  44917. * @param {Object} activeXhrs - an object that tracks all XHR requests
  44918. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  44919. * @param {Function} doneFn - a callback that is executed after all resources have been
  44920. * downloaded and any decryption completed
  44921. */
  44922. var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
  44923. var count = 0;
  44924. var didError = false;
  44925. return function (error, segment) {
  44926. if (didError) {
  44927. return;
  44928. }
  44929. if (error) {
  44930. didError = true; // If there are errors, we have to abort any outstanding requests
  44931. abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
  44932. // handle the aborted events from those requests, there are some cases where we may
  44933. // never get an aborted event. For instance, if the network connection is lost and
  44934. // there were two requests, the first may have triggered an error immediately, while
  44935. // the second request remains unsent. In that case, the aborted algorithm will not
  44936. // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
  44937. //
  44938. // We also can't rely on the ready state of the XHR, since the request that
  44939. // triggered the connection error may also show as a ready state of 0 (unsent).
  44940. // Therefore, we have to finish this group of requests immediately after the first
  44941. // seen error.
  44942. return doneFn(error, segment);
  44943. }
  44944. count += 1;
  44945. if (count === activeXhrs.length) {
  44946. // Keep track of when *all* of the requests have completed
  44947. segment.endOfAllRequests = Date.now();
  44948. if (segment.encryptedBytes) {
  44949. return decryptSegment(decrypter, segment, doneFn);
  44950. } // Otherwise, everything is ready just continue
  44951. return doneFn(null, segment);
  44952. }
  44953. };
  44954. };
  44955. /**
  44956. * Simple progress event callback handler that gathers some stats before
  44957. * executing a provided callback with the `segment` object
  44958. *
  44959. * @param {Object} segment - a simplified copy of the segmentInfo object
  44960. * from SegmentLoader
  44961. * @param {Function} progressFn - a callback that is executed each time a progress event
  44962. * is received
  44963. * @param {Event} event - the progress event object from XMLHttpRequest
  44964. */
  44965. var handleProgress = function handleProgress(segment, progressFn) {
  44966. return function (event) {
  44967. segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
  44968. if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
  44969. segment.stats.firstBytesReceivedAt = Date.now();
  44970. }
  44971. return progressFn(event, segment);
  44972. };
  44973. };
  44974. /**
  44975. * Load all resources and does any processing necessary for a media-segment
  44976. *
  44977. * Features:
  44978. * decrypts the media-segment if it has a key uri and an iv
  44979. * aborts *all* requests if *any* one request fails
  44980. *
  44981. * The segment object, at minimum, has the following format:
  44982. * {
  44983. * resolvedUri: String,
  44984. * [byterange]: {
  44985. * offset: Number,
  44986. * length: Number
  44987. * },
  44988. * [key]: {
  44989. * resolvedUri: String
  44990. * [byterange]: {
  44991. * offset: Number,
  44992. * length: Number
  44993. * },
  44994. * iv: {
  44995. * bytes: Uint32Array
  44996. * }
  44997. * },
  44998. * [map]: {
  44999. * resolvedUri: String,
  45000. * [byterange]: {
  45001. * offset: Number,
  45002. * length: Number
  45003. * },
  45004. * [bytes]: Uint8Array
  45005. * }
  45006. * }
  45007. * ...where [name] denotes optional properties
  45008. *
  45009. * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
  45010. * @param {Object} xhrOptions - the base options to provide to all xhr requests
  45011. * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
  45012. * decryption routines
  45013. * @param {Object} segment - a simplified copy of the segmentInfo object
  45014. * from SegmentLoader
  45015. * @param {Function} progressFn - a callback that receives progress events from the main
  45016. * segment's xhr request
  45017. * @param {Function} doneFn - a callback that is executed only once all requests have
  45018. * succeeded or failed
  45019. * @returns {Function} a function that, when invoked, immediately aborts all
  45020. * outstanding requests
  45021. */
  45022. var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) {
  45023. var activeXhrs = [];
  45024. var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key
  45025. if (segment.key && !segment.key.bytes) {
  45026. var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  45027. uri: segment.key.resolvedUri,
  45028. responseType: 'arraybuffer'
  45029. });
  45030. var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
  45031. var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
  45032. activeXhrs.push(keyXhr);
  45033. } // optionally, request the associated media init segment
  45034. if (segment.map && !segment.map.bytes) {
  45035. var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
  45036. uri: segment.map.resolvedUri,
  45037. responseType: 'arraybuffer',
  45038. headers: segmentXhrHeaders(segment.map)
  45039. });
  45040. var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn);
  45041. var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
  45042. activeXhrs.push(initSegmentXhr);
  45043. }
  45044. var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  45045. uri: segment.resolvedUri,
  45046. responseType: 'arraybuffer',
  45047. headers: segmentXhrHeaders(segment)
  45048. });
  45049. var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn);
  45050. var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
  45051. segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
  45052. activeXhrs.push(segmentXhr);
  45053. return function () {
  45054. return abortAll(activeXhrs);
  45055. };
  45056. }; // Utilities
  45057. /**
  45058. * Returns the CSS value for the specified property on an element
  45059. * using `getComputedStyle`. Firefox has a long-standing issue where
  45060. * getComputedStyle() may return null when running in an iframe with
  45061. * `display: none`.
  45062. *
  45063. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  45064. * @param {HTMLElement} el the htmlelement to work on
  45065. * @param {string} the proprety to get the style for
  45066. */
  45067. var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
  45068. var result = void 0;
  45069. if (!el) {
  45070. return '';
  45071. }
  45072. result = window$1.getComputedStyle(el);
  45073. if (!result) {
  45074. return '';
  45075. }
  45076. return result[property];
  45077. };
  45078. /**
  45079. * Resuable stable sort function
  45080. *
  45081. * @param {Playlists} array
  45082. * @param {Function} sortFn Different comparators
  45083. * @function stableSort
  45084. */
  45085. var stableSort = function stableSort(array, sortFn) {
  45086. var newArray = array.slice();
  45087. array.sort(function (left, right) {
  45088. var cmp = sortFn(left, right);
  45089. if (cmp === 0) {
  45090. return newArray.indexOf(left) - newArray.indexOf(right);
  45091. }
  45092. return cmp;
  45093. });
  45094. };
  45095. /**
  45096. * A comparator function to sort two playlist object by bandwidth.
  45097. *
  45098. * @param {Object} left a media playlist object
  45099. * @param {Object} right a media playlist object
  45100. * @return {Number} Greater than zero if the bandwidth attribute of
  45101. * left is greater than the corresponding attribute of right. Less
  45102. * than zero if the bandwidth of right is greater than left and
  45103. * exactly zero if the two are equal.
  45104. */
  45105. var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
  45106. var leftBandwidth = void 0;
  45107. var rightBandwidth = void 0;
  45108. if (left.attributes.BANDWIDTH) {
  45109. leftBandwidth = left.attributes.BANDWIDTH;
  45110. }
  45111. leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
  45112. if (right.attributes.BANDWIDTH) {
  45113. rightBandwidth = right.attributes.BANDWIDTH;
  45114. }
  45115. rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
  45116. return leftBandwidth - rightBandwidth;
  45117. };
  45118. /**
  45119. * A comparator function to sort two playlist object by resolution (width).
  45120. * @param {Object} left a media playlist object
  45121. * @param {Object} right a media playlist object
  45122. * @return {Number} Greater than zero if the resolution.width attribute of
  45123. * left is greater than the corresponding attribute of right. Less
  45124. * than zero if the resolution.width of right is greater than left and
  45125. * exactly zero if the two are equal.
  45126. */
  45127. var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
  45128. var leftWidth = void 0;
  45129. var rightWidth = void 0;
  45130. if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
  45131. leftWidth = left.attributes.RESOLUTION.width;
  45132. }
  45133. leftWidth = leftWidth || window$1.Number.MAX_VALUE;
  45134. if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
  45135. rightWidth = right.attributes.RESOLUTION.width;
  45136. }
  45137. rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
  45138. // have the same media dimensions/ resolution
  45139. if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
  45140. return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
  45141. }
  45142. return leftWidth - rightWidth;
  45143. };
  45144. /**
  45145. * Chooses the appropriate media playlist based on bandwidth and player size
  45146. *
  45147. * @param {Object} master
  45148. * Object representation of the master manifest
  45149. * @param {Number} playerBandwidth
  45150. * Current calculated bandwidth of the player
  45151. * @param {Number} playerWidth
  45152. * Current width of the player element
  45153. * @param {Number} playerHeight
  45154. * Current height of the player element
  45155. * @param {Boolean} limitRenditionByPlayerDimensions
  45156. * True if the player width and height should be used during the selection, false otherwise
  45157. * @return {Playlist} the highest bitrate playlist less than the
  45158. * currently detected bandwidth, accounting for some amount of
  45159. * bandwidth variance
  45160. */
  45161. var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) {
  45162. // convert the playlists to an intermediary representation to make comparisons easier
  45163. var sortedPlaylistReps = master.playlists.map(function (playlist) {
  45164. var width = void 0;
  45165. var height = void 0;
  45166. var bandwidth = void 0;
  45167. width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
  45168. height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
  45169. bandwidth = playlist.attributes.BANDWIDTH;
  45170. bandwidth = bandwidth || window$1.Number.MAX_VALUE;
  45171. return {
  45172. bandwidth: bandwidth,
  45173. width: width,
  45174. height: height,
  45175. playlist: playlist
  45176. };
  45177. });
  45178. stableSort(sortedPlaylistReps, function (left, right) {
  45179. return left.bandwidth - right.bandwidth;
  45180. }); // filter out any playlists that have been excluded due to
  45181. // incompatible configurations
  45182. sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  45183. return !Playlist.isIncompatible(rep.playlist);
  45184. }); // filter out any playlists that have been disabled manually through the representations
  45185. // api or blacklisted temporarily due to playback errors.
  45186. var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  45187. return Playlist.isEnabled(rep.playlist);
  45188. });
  45189. if (!enabledPlaylistReps.length) {
  45190. // if there are no enabled playlists, then they have all been blacklisted or disabled
  45191. // by the user through the representations api. In this case, ignore blacklisting and
  45192. // fallback to what the user wants by using playlists the user has not disabled.
  45193. enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  45194. return !Playlist.isDisabled(rep.playlist);
  45195. });
  45196. } // filter out any variant that has greater effective bitrate
  45197. // than the current estimated bandwidth
  45198. var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
  45199. return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
  45200. });
  45201. var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
  45202. // and then taking the very first element
  45203. var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
  45204. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  45205. })[0]; // if we're not going to limit renditions by player size, make an early decision.
  45206. if (limitRenditionByPlayerDimensions === false) {
  45207. var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  45208. return _chosenRep ? _chosenRep.playlist : null;
  45209. } // filter out playlists without resolution information
  45210. var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
  45211. return rep.width && rep.height;
  45212. }); // sort variants by resolution
  45213. stableSort(haveResolution, function (left, right) {
  45214. return left.width - right.width;
  45215. }); // if we have the exact resolution as the player use it
  45216. var resolutionBestRepList = haveResolution.filter(function (rep) {
  45217. return rep.width === playerWidth && rep.height === playerHeight;
  45218. });
  45219. highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
  45220. var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
  45221. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  45222. })[0];
  45223. var resolutionPlusOneList = void 0;
  45224. var resolutionPlusOneSmallest = void 0;
  45225. var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player
  45226. // if there is no match of exact resolution
  45227. if (!resolutionBestRep) {
  45228. resolutionPlusOneList = haveResolution.filter(function (rep) {
  45229. return rep.width > playerWidth || rep.height > playerHeight;
  45230. }); // find all the variants have the same smallest resolution
  45231. resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
  45232. return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
  45233. }); // ensure that we also pick the highest bandwidth variant that
  45234. // is just-larger-than the video player
  45235. highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
  45236. resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
  45237. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  45238. })[0];
  45239. } // fallback chain of variants
  45240. var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  45241. return chosenRep ? chosenRep.playlist : null;
  45242. }; // Playlist Selectors
  45243. /**
  45244. * Chooses the appropriate media playlist based on the most recent
  45245. * bandwidth estimate and the player size.
  45246. *
  45247. * Expects to be called within the context of an instance of HlsHandler
  45248. *
  45249. * @return {Playlist} the highest bitrate playlist less than the
  45250. * currently detected bandwidth, accounting for some amount of
  45251. * bandwidth variance
  45252. */
  45253. var lastBandwidthSelector = function lastBandwidthSelector() {
  45254. return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions);
  45255. };
  45256. /**
  45257. * Chooses the appropriate media playlist based on the potential to rebuffer
  45258. *
  45259. * @param {Object} settings
  45260. * Object of information required to use this selector
  45261. * @param {Object} settings.master
  45262. * Object representation of the master manifest
  45263. * @param {Number} settings.currentTime
  45264. * The current time of the player
  45265. * @param {Number} settings.bandwidth
  45266. * Current measured bandwidth
  45267. * @param {Number} settings.duration
  45268. * Duration of the media
  45269. * @param {Number} settings.segmentDuration
  45270. * Segment duration to be used in round trip time calculations
  45271. * @param {Number} settings.timeUntilRebuffer
  45272. * Time left in seconds until the player has to rebuffer
  45273. * @param {Number} settings.currentTimeline
  45274. * The current timeline segments are being loaded from
  45275. * @param {SyncController} settings.syncController
  45276. * SyncController for determining if we have a sync point for a given playlist
  45277. * @return {Object|null}
  45278. * {Object} return.playlist
  45279. * The highest bandwidth playlist with the least amount of rebuffering
  45280. * {Number} return.rebufferingImpact
  45281. * The amount of time in seconds switching to this playlist will rebuffer. A
  45282. * negative value means that switching will cause zero rebuffering.
  45283. */
  45284. var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
  45285. var master = settings.master,
  45286. currentTime = settings.currentTime,
  45287. bandwidth = settings.bandwidth,
  45288. duration$$1 = settings.duration,
  45289. segmentDuration = settings.segmentDuration,
  45290. timeUntilRebuffer = settings.timeUntilRebuffer,
  45291. currentTimeline = settings.currentTimeline,
  45292. syncController = settings.syncController; // filter out any playlists that have been excluded due to
  45293. // incompatible configurations
  45294. var compatiblePlaylists = master.playlists.filter(function (playlist) {
  45295. return !Playlist.isIncompatible(playlist);
  45296. }); // filter out any playlists that have been disabled manually through the representations
  45297. // api or blacklisted temporarily due to playback errors.
  45298. var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
  45299. if (!enabledPlaylists.length) {
  45300. // if there are no enabled playlists, then they have all been blacklisted or disabled
  45301. // by the user through the representations api. In this case, ignore blacklisting and
  45302. // fallback to what the user wants by using playlists the user has not disabled.
  45303. enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
  45304. return !Playlist.isDisabled(playlist);
  45305. });
  45306. }
  45307. var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
  45308. var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
  45309. var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
  45310. // sync request first. This will double the request time
  45311. var numRequests = syncPoint ? 1 : 2;
  45312. var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
  45313. var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
  45314. return {
  45315. playlist: playlist,
  45316. rebufferingImpact: rebufferingImpact
  45317. };
  45318. });
  45319. var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
  45320. return estimate.rebufferingImpact <= 0;
  45321. }); // Sort by bandwidth DESC
  45322. stableSort(noRebufferingPlaylists, function (a, b) {
  45323. return comparePlaylistBandwidth(b.playlist, a.playlist);
  45324. });
  45325. if (noRebufferingPlaylists.length) {
  45326. return noRebufferingPlaylists[0];
  45327. }
  45328. stableSort(rebufferingEstimates, function (a, b) {
  45329. return a.rebufferingImpact - b.rebufferingImpact;
  45330. });
  45331. return rebufferingEstimates[0] || null;
  45332. };
  45333. /**
  45334. * Chooses the appropriate media playlist, which in this case is the lowest bitrate
  45335. * one with video. If no renditions with video exist, return the lowest audio rendition.
  45336. *
  45337. * Expects to be called within the context of an instance of HlsHandler
  45338. *
  45339. * @return {Object|null}
  45340. * {Object} return.playlist
  45341. * The lowest bitrate playlist that contains a video codec. If no such rendition
  45342. * exists pick the lowest audio rendition.
  45343. */
  45344. var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
  45345. // filter out any playlists that have been excluded due to
  45346. // incompatible configurations or playback errors
  45347. var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
  45348. stableSort(playlists, function (a, b) {
  45349. return comparePlaylistBandwidth(a, b);
  45350. }); // Parse and assume that playlists with no video codec have no video
  45351. // (this is not necessarily true, although it is generally true).
  45352. //
  45353. // If an entire manifest has no valid videos everything will get filtered
  45354. // out.
  45355. var playlistsWithVideo = playlists.filter(function (playlist) {
  45356. return parseCodecs(playlist.attributes.CODECS).videoCodec;
  45357. });
  45358. return playlistsWithVideo[0] || null;
  45359. };
  45360. /**
  45361. * Create captions text tracks on video.js if they do not exist
  45362. *
  45363. * @param {Object} inbandTextTracks a reference to current inbandTextTracks
  45364. * @param {Object} tech the video.js tech
  45365. * @param {Object} captionStreams the caption streams to create
  45366. * @private
  45367. */
  45368. var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) {
  45369. for (var trackId in captionStreams) {
  45370. if (!inbandTextTracks[trackId]) {
  45371. tech.trigger({
  45372. type: 'usage',
  45373. name: 'hls-608'
  45374. });
  45375. var track = tech.textTracks().getTrackById(trackId);
  45376. if (track) {
  45377. // Resuse an existing track with a CC# id because this was
  45378. // very likely created by videojs-contrib-hls from information
  45379. // in the m3u8 for us to use
  45380. inbandTextTracks[trackId] = track;
  45381. } else {
  45382. // Otherwise, create a track with the default `CC#` label and
  45383. // without a language
  45384. inbandTextTracks[trackId] = tech.addRemoteTextTrack({
  45385. kind: 'captions',
  45386. id: trackId,
  45387. label: trackId
  45388. }, false).track;
  45389. }
  45390. }
  45391. }
  45392. };
  45393. var addCaptionData = function addCaptionData(_ref) {
  45394. var inbandTextTracks = _ref.inbandTextTracks,
  45395. captionArray = _ref.captionArray,
  45396. timestampOffset = _ref.timestampOffset;
  45397. if (!captionArray) {
  45398. return;
  45399. }
  45400. var Cue = window.WebKitDataCue || window.VTTCue;
  45401. captionArray.forEach(function (caption) {
  45402. var track = caption.stream;
  45403. var startTime = caption.startTime;
  45404. var endTime = caption.endTime;
  45405. if (!inbandTextTracks[track]) {
  45406. return;
  45407. }
  45408. startTime += timestampOffset;
  45409. endTime += timestampOffset;
  45410. inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text));
  45411. });
  45412. };
  45413. /**
  45414. * @file segment-loader.js
  45415. */
  45416. // in ms
  45417. var CHECK_BUFFER_DELAY = 500;
  45418. /**
  45419. * Determines if we should call endOfStream on the media source based
  45420. * on the state of the buffer or if appened segment was the final
  45421. * segment in the playlist.
  45422. *
  45423. * @param {Object} playlist a media playlist object
  45424. * @param {Object} mediaSource the MediaSource object
  45425. * @param {Number} segmentIndex the index of segment we last appended
  45426. * @returns {Boolean} do we need to call endOfStream on the MediaSource
  45427. */
  45428. var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
  45429. if (!playlist || !mediaSource) {
  45430. return false;
  45431. }
  45432. var segments = playlist.segments; // determine a few boolean values to help make the branch below easier
  45433. // to read
  45434. var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream
  45435. // so that MediaSources can trigger the `ended` event when it runs out of
  45436. // buffered data instead of waiting for me
  45437. return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
  45438. };
  45439. var finite = function finite(num) {
  45440. return typeof num === 'number' && isFinite(num);
  45441. };
  45442. var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
  45443. // Although these checks should most likely cover non 'main' types, for now it narrows
  45444. // the scope of our checks.
  45445. if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
  45446. return null;
  45447. }
  45448. if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
  45449. return 'Neither audio nor video found in segment.';
  45450. }
  45451. if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
  45452. 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.';
  45453. }
  45454. if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
  45455. 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.';
  45456. }
  45457. return null;
  45458. };
  45459. /**
  45460. * Calculates a time value that is safe to remove from the back buffer without interupting
  45461. * playback.
  45462. *
  45463. * @param {TimeRange} seekable
  45464. * The current seekable range
  45465. * @param {Number} currentTime
  45466. * The current time of the player
  45467. * @param {Number} targetDuration
  45468. * The target duration of the current playlist
  45469. * @return {Number}
  45470. * Time that is safe to remove from the back buffer without interupting playback
  45471. */
  45472. var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
  45473. var removeToTime = void 0;
  45474. if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
  45475. // If we have a seekable range use that as the limit for what can be removed safely
  45476. removeToTime = seekable$$1.start(0);
  45477. } else {
  45478. // otherwise remove anything older than 30 seconds before the current play head
  45479. removeToTime = currentTime - 30;
  45480. } // Don't allow removing from the buffer within target duration of current time
  45481. // to avoid the possibility of removing the GOP currently being played which could
  45482. // cause playback stalls.
  45483. return Math.min(removeToTime, currentTime - targetDuration);
  45484. };
  45485. var segmentInfoString = function segmentInfoString(segmentInfo) {
  45486. var _segmentInfo$segment = segmentInfo.segment,
  45487. start = _segmentInfo$segment.start,
  45488. end = _segmentInfo$segment.end,
  45489. _segmentInfo$playlist = segmentInfo.playlist,
  45490. seq = _segmentInfo$playlist.mediaSequence,
  45491. id = _segmentInfo$playlist.id,
  45492. _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
  45493. segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
  45494. index = segmentInfo.mediaIndex,
  45495. timeline = segmentInfo.timeline;
  45496. return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
  45497. };
  45498. /**
  45499. * An object that manages segment loading and appending.
  45500. *
  45501. * @class SegmentLoader
  45502. * @param {Object} options required and optional options
  45503. * @extends videojs.EventTarget
  45504. */
  45505. var SegmentLoader = function (_videojs$EventTarget) {
  45506. inherits$1(SegmentLoader, _videojs$EventTarget);
  45507. function SegmentLoader(settings) {
  45508. classCallCheck$1(this, SegmentLoader); // check pre-conditions
  45509. var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
  45510. if (!settings) {
  45511. throw new TypeError('Initialization settings are required');
  45512. }
  45513. if (typeof settings.currentTime !== 'function') {
  45514. throw new TypeError('No currentTime getter specified');
  45515. }
  45516. if (!settings.mediaSource) {
  45517. throw new TypeError('No MediaSource specified');
  45518. } // public properties
  45519. _this.bandwidth = settings.bandwidth;
  45520. _this.throughput = {
  45521. rate: 0,
  45522. count: 0
  45523. };
  45524. _this.roundTrip = NaN;
  45525. _this.resetStats_();
  45526. _this.mediaIndex = null; // private settings
  45527. _this.hasPlayed_ = settings.hasPlayed;
  45528. _this.currentTime_ = settings.currentTime;
  45529. _this.seekable_ = settings.seekable;
  45530. _this.seeking_ = settings.seeking;
  45531. _this.duration_ = settings.duration;
  45532. _this.mediaSource_ = settings.mediaSource;
  45533. _this.hls_ = settings.hls;
  45534. _this.loaderType_ = settings.loaderType;
  45535. _this.startingMedia_ = void 0;
  45536. _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
  45537. _this.goalBufferLength_ = settings.goalBufferLength;
  45538. _this.sourceType_ = settings.sourceType;
  45539. _this.inbandTextTracks_ = settings.inbandTextTracks;
  45540. _this.state_ = 'INIT'; // private instance variables
  45541. _this.checkBufferTimeout_ = null;
  45542. _this.error_ = void 0;
  45543. _this.currentTimeline_ = -1;
  45544. _this.pendingSegment_ = null;
  45545. _this.mimeType_ = null;
  45546. _this.sourceUpdater_ = null;
  45547. _this.xhrOptions_ = null; // Fragmented mp4 playback
  45548. _this.activeInitSegmentId_ = null;
  45549. _this.initSegments_ = {}; // HLSe playback
  45550. _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
  45551. _this.keyCache_ = {}; // Fmp4 CaptionParser
  45552. if (_this.loaderType_ === 'main') {
  45553. _this.captionParser_ = new mp4_6();
  45554. } else {
  45555. _this.captionParser_ = null;
  45556. }
  45557. _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
  45558. // between a time in the display time and a segment index within
  45559. // a playlist
  45560. _this.syncController_ = settings.syncController;
  45561. _this.syncPoint_ = {
  45562. segmentIndex: 0,
  45563. time: 0
  45564. };
  45565. _this.syncController_.on('syncinfoupdate', function () {
  45566. return _this.trigger('syncinfoupdate');
  45567. });
  45568. _this.mediaSource_.addEventListener('sourceopen', function () {
  45569. return _this.ended_ = false;
  45570. }); // ...for determining the fetch location
  45571. _this.fetchAtBuffer_ = false;
  45572. _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
  45573. Object.defineProperty(_this, 'state', {
  45574. get: function get$$1() {
  45575. return this.state_;
  45576. },
  45577. set: function set$$1(newState) {
  45578. if (newState !== this.state_) {
  45579. this.logger_(this.state_ + ' -> ' + newState);
  45580. this.state_ = newState;
  45581. }
  45582. }
  45583. });
  45584. return _this;
  45585. }
  45586. /**
  45587. * reset all of our media stats
  45588. *
  45589. * @private
  45590. */
  45591. createClass$1(SegmentLoader, [{
  45592. key: 'resetStats_',
  45593. value: function resetStats_() {
  45594. this.mediaBytesTransferred = 0;
  45595. this.mediaRequests = 0;
  45596. this.mediaRequestsAborted = 0;
  45597. this.mediaRequestsTimedout = 0;
  45598. this.mediaRequestsErrored = 0;
  45599. this.mediaTransferDuration = 0;
  45600. this.mediaSecondsLoaded = 0;
  45601. }
  45602. /**
  45603. * dispose of the SegmentLoader and reset to the default state
  45604. */
  45605. }, {
  45606. key: 'dispose',
  45607. value: function dispose() {
  45608. this.state = 'DISPOSED';
  45609. this.pause();
  45610. this.abort_();
  45611. if (this.sourceUpdater_) {
  45612. this.sourceUpdater_.dispose();
  45613. }
  45614. this.resetStats_();
  45615. if (this.captionParser_) {
  45616. this.captionParser_.reset();
  45617. }
  45618. }
  45619. /**
  45620. * abort anything that is currently doing on with the SegmentLoader
  45621. * and reset to a default state
  45622. */
  45623. }, {
  45624. key: 'abort',
  45625. value: function abort() {
  45626. if (this.state !== 'WAITING') {
  45627. if (this.pendingSegment_) {
  45628. this.pendingSegment_ = null;
  45629. }
  45630. return;
  45631. }
  45632. this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
  45633. // since we are no longer "waiting" on any requests. XHR callback is not always run
  45634. // when the request is aborted. This will prevent the loader from being stuck in the
  45635. // WAITING state indefinitely.
  45636. this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
  45637. // next segment
  45638. if (!this.paused()) {
  45639. this.monitorBuffer_();
  45640. }
  45641. }
  45642. /**
  45643. * abort all pending xhr requests and null any pending segements
  45644. *
  45645. * @private
  45646. */
  45647. }, {
  45648. key: 'abort_',
  45649. value: function abort_() {
  45650. if (this.pendingSegment_) {
  45651. this.pendingSegment_.abortRequests();
  45652. } // clear out the segment being processed
  45653. this.pendingSegment_ = null;
  45654. }
  45655. /**
  45656. * set an error on the segment loader and null out any pending segements
  45657. *
  45658. * @param {Error} error the error to set on the SegmentLoader
  45659. * @return {Error} the error that was set or that is currently set
  45660. */
  45661. }, {
  45662. key: 'error',
  45663. value: function error(_error) {
  45664. if (typeof _error !== 'undefined') {
  45665. this.error_ = _error;
  45666. }
  45667. this.pendingSegment_ = null;
  45668. return this.error_;
  45669. }
  45670. }, {
  45671. key: 'endOfStream',
  45672. value: function endOfStream() {
  45673. this.ended_ = true;
  45674. this.pause();
  45675. this.trigger('ended');
  45676. }
  45677. /**
  45678. * Indicates which time ranges are buffered
  45679. *
  45680. * @return {TimeRange}
  45681. * TimeRange object representing the current buffered ranges
  45682. */
  45683. }, {
  45684. key: 'buffered_',
  45685. value: function buffered_() {
  45686. if (!this.sourceUpdater_) {
  45687. return videojs$1.createTimeRanges();
  45688. }
  45689. return this.sourceUpdater_.buffered();
  45690. }
  45691. /**
  45692. * Gets and sets init segment for the provided map
  45693. *
  45694. * @param {Object} map
  45695. * The map object representing the init segment to get or set
  45696. * @param {Boolean=} set
  45697. * If true, the init segment for the provided map should be saved
  45698. * @return {Object}
  45699. * map object for desired init segment
  45700. */
  45701. }, {
  45702. key: 'initSegment',
  45703. value: function initSegment(map) {
  45704. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  45705. if (!map) {
  45706. return null;
  45707. }
  45708. var id = initSegmentId(map);
  45709. var storedMap = this.initSegments_[id];
  45710. if (set$$1 && !storedMap && map.bytes) {
  45711. this.initSegments_[id] = storedMap = {
  45712. resolvedUri: map.resolvedUri,
  45713. byterange: map.byterange,
  45714. bytes: map.bytes,
  45715. timescales: map.timescales,
  45716. videoTrackIds: map.videoTrackIds
  45717. };
  45718. }
  45719. return storedMap || map;
  45720. }
  45721. /**
  45722. * Gets and sets key for the provided key
  45723. *
  45724. * @param {Object} key
  45725. * The key object representing the key to get or set
  45726. * @param {Boolean=} set
  45727. * If true, the key for the provided key should be saved
  45728. * @return {Object}
  45729. * Key object for desired key
  45730. */
  45731. }, {
  45732. key: 'segmentKey',
  45733. value: function segmentKey(key) {
  45734. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  45735. if (!key) {
  45736. return null;
  45737. }
  45738. var id = segmentKeyId(key);
  45739. var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
  45740. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
  45741. if (this.cacheEncryptionKeys_ && set$$1 && !storedKey && key.bytes) {
  45742. this.keyCache_[id] = storedKey = {
  45743. resolvedUri: key.resolvedUri,
  45744. bytes: key.bytes
  45745. };
  45746. }
  45747. var result = {
  45748. resolvedUri: (storedKey || key).resolvedUri
  45749. };
  45750. if (storedKey) {
  45751. result.bytes = storedKey.bytes;
  45752. }
  45753. return result;
  45754. }
  45755. /**
  45756. * Returns true if all configuration required for loading is present, otherwise false.
  45757. *
  45758. * @return {Boolean} True if the all configuration is ready for loading
  45759. * @private
  45760. */
  45761. }, {
  45762. key: 'couldBeginLoading_',
  45763. value: function couldBeginLoading_() {
  45764. return this.playlist_ && ( // the source updater is created when init_ is called, so either having a
  45765. // source updater or being in the INIT state with a mimeType is enough
  45766. // to say we have all the needed configuration to start loading.
  45767. this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
  45768. }
  45769. /**
  45770. * load a playlist and start to fill the buffer
  45771. */
  45772. }, {
  45773. key: 'load',
  45774. value: function load() {
  45775. // un-pause
  45776. this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
  45777. // specified
  45778. if (!this.playlist_) {
  45779. return;
  45780. } // not sure if this is the best place for this
  45781. this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading
  45782. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  45783. return this.init_();
  45784. } // if we're in the middle of processing a segment already, don't
  45785. // kick off an additional segment request
  45786. if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
  45787. return;
  45788. }
  45789. this.state = 'READY';
  45790. }
  45791. /**
  45792. * Once all the starting parameters have been specified, begin
  45793. * operation. This method should only be invoked from the INIT
  45794. * state.
  45795. *
  45796. * @private
  45797. */
  45798. }, {
  45799. key: 'init_',
  45800. value: function init_() {
  45801. this.state = 'READY';
  45802. this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
  45803. this.resetEverything();
  45804. return this.monitorBuffer_();
  45805. }
  45806. /**
  45807. * set a playlist on the segment loader
  45808. *
  45809. * @param {PlaylistLoader} media the playlist to set on the segment loader
  45810. */
  45811. }, {
  45812. key: 'playlist',
  45813. value: function playlist(newPlaylist) {
  45814. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  45815. if (!newPlaylist) {
  45816. return;
  45817. }
  45818. var oldPlaylist = this.playlist_;
  45819. var segmentInfo = this.pendingSegment_;
  45820. this.playlist_ = newPlaylist;
  45821. this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
  45822. // is always our zero-time so force a sync update each time the playlist
  45823. // is refreshed from the server
  45824. if (!this.hasPlayed_()) {
  45825. newPlaylist.syncInfo = {
  45826. mediaSequence: newPlaylist.mediaSequence,
  45827. time: 0
  45828. };
  45829. }
  45830. var oldId = null;
  45831. if (oldPlaylist) {
  45832. if (oldPlaylist.id) {
  45833. oldId = oldPlaylist.id;
  45834. } else if (oldPlaylist.uri) {
  45835. oldId = oldPlaylist.uri;
  45836. }
  45837. }
  45838. this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
  45839. // in LIVE, we always want to update with new playlists (including refreshes)
  45840. this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
  45841. // buffering now
  45842. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  45843. return this.init_();
  45844. }
  45845. if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
  45846. if (this.mediaIndex !== null) {
  45847. // we must "resync" the segment loader when we switch renditions and
  45848. // the segment loader is already synced to the previous rendition
  45849. this.resyncLoader();
  45850. } // the rest of this function depends on `oldPlaylist` being defined
  45851. return;
  45852. } // we reloaded the same playlist so we are in a live scenario
  45853. // and we will likely need to adjust the mediaIndex
  45854. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
  45855. this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader
  45856. // this is important because we can abort a request and this value must be
  45857. // equal to the last appended mediaIndex
  45858. if (this.mediaIndex !== null) {
  45859. this.mediaIndex -= mediaSequenceDiff;
  45860. } // update the mediaIndex on the SegmentInfo object
  45861. // this is important because we will update this.mediaIndex with this value
  45862. // in `handleUpdateEnd_` after the segment has been successfully appended
  45863. if (segmentInfo) {
  45864. segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is
  45865. // saved for the new playlist's segment, however, if the segment fell off the
  45866. // playlist, we can leave the old reference and just lose the timing info
  45867. if (segmentInfo.mediaIndex >= 0) {
  45868. segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
  45869. }
  45870. }
  45871. this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
  45872. }
  45873. /**
  45874. * Prevent the loader from fetching additional segments. If there
  45875. * is a segment request outstanding, it will finish processing
  45876. * before the loader halts. A segment loader can be unpaused by
  45877. * calling load().
  45878. */
  45879. }, {
  45880. key: 'pause',
  45881. value: function pause() {
  45882. if (this.checkBufferTimeout_) {
  45883. window$1.clearTimeout(this.checkBufferTimeout_);
  45884. this.checkBufferTimeout_ = null;
  45885. }
  45886. }
  45887. /**
  45888. * Returns whether the segment loader is fetching additional
  45889. * segments when given the opportunity. This property can be
  45890. * modified through calls to pause() and load().
  45891. */
  45892. }, {
  45893. key: 'paused',
  45894. value: function paused() {
  45895. return this.checkBufferTimeout_ === null;
  45896. }
  45897. /**
  45898. * create/set the following mimetype on the SourceBuffer through a
  45899. * SourceUpdater
  45900. *
  45901. * @param {String} mimeType the mime type string to use
  45902. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
  45903. * is added to the media source
  45904. */
  45905. }, {
  45906. key: 'mimeType',
  45907. value: function mimeType(_mimeType, sourceBufferEmitter) {
  45908. if (this.mimeType_) {
  45909. return;
  45910. }
  45911. this.mimeType_ = _mimeType;
  45912. this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start
  45913. // buffering now
  45914. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  45915. this.init_();
  45916. }
  45917. }
  45918. /**
  45919. * Delete all the buffered data and reset the SegmentLoader
  45920. * @param {Function} [done] an optional callback to be executed when the remove
  45921. * operation is complete
  45922. */
  45923. }, {
  45924. key: 'resetEverything',
  45925. value: function resetEverything(done) {
  45926. this.ended_ = false;
  45927. this.resetLoader();
  45928. this.remove(0, this.duration_(), done); // clears fmp4 captions
  45929. if (this.captionParser_) {
  45930. this.captionParser_.clearAllCaptions();
  45931. }
  45932. this.trigger('reseteverything');
  45933. }
  45934. /**
  45935. * Force the SegmentLoader to resync and start loading around the currentTime instead
  45936. * of starting at the end of the buffer
  45937. *
  45938. * Useful for fast quality changes
  45939. */
  45940. }, {
  45941. key: 'resetLoader',
  45942. value: function resetLoader() {
  45943. this.fetchAtBuffer_ = false;
  45944. this.resyncLoader();
  45945. }
  45946. /**
  45947. * Force the SegmentLoader to restart synchronization and make a conservative guess
  45948. * before returning to the simple walk-forward method
  45949. */
  45950. }, {
  45951. key: 'resyncLoader',
  45952. value: function resyncLoader() {
  45953. this.mediaIndex = null;
  45954. this.syncPoint_ = null;
  45955. this.abort();
  45956. }
  45957. /**
  45958. * Remove any data in the source buffer between start and end times
  45959. * @param {Number} start - the start time of the region to remove from the buffer
  45960. * @param {Number} end - the end time of the region to remove from the buffer
  45961. * @param {Function} [done] - an optional callback to be executed when the remove
  45962. * operation is complete
  45963. */
  45964. }, {
  45965. key: 'remove',
  45966. value: function remove(start, end, done) {
  45967. if (this.sourceUpdater_) {
  45968. this.sourceUpdater_.remove(start, end, done);
  45969. }
  45970. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  45971. if (this.inbandTextTracks_) {
  45972. for (var id in this.inbandTextTracks_) {
  45973. removeCuesFromTrack(start, end, this.inbandTextTracks_[id]);
  45974. }
  45975. }
  45976. }
  45977. /**
  45978. * (re-)schedule monitorBufferTick_ to run as soon as possible
  45979. *
  45980. * @private
  45981. */
  45982. }, {
  45983. key: 'monitorBuffer_',
  45984. value: function monitorBuffer_() {
  45985. if (this.checkBufferTimeout_) {
  45986. window$1.clearTimeout(this.checkBufferTimeout_);
  45987. }
  45988. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
  45989. }
  45990. /**
  45991. * As long as the SegmentLoader is in the READY state, periodically
  45992. * invoke fillBuffer_().
  45993. *
  45994. * @private
  45995. */
  45996. }, {
  45997. key: 'monitorBufferTick_',
  45998. value: function monitorBufferTick_() {
  45999. if (this.state === 'READY') {
  46000. this.fillBuffer_();
  46001. }
  46002. if (this.checkBufferTimeout_) {
  46003. window$1.clearTimeout(this.checkBufferTimeout_);
  46004. }
  46005. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
  46006. }
  46007. /**
  46008. * fill the buffer with segements unless the sourceBuffers are
  46009. * currently updating
  46010. *
  46011. * Note: this function should only ever be called by monitorBuffer_
  46012. * and never directly
  46013. *
  46014. * @private
  46015. */
  46016. }, {
  46017. key: 'fillBuffer_',
  46018. value: function fillBuffer_() {
  46019. if (this.sourceUpdater_.updating()) {
  46020. return;
  46021. }
  46022. if (!this.syncPoint_) {
  46023. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  46024. } // see if we need to begin loading immediately
  46025. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  46026. if (!segmentInfo) {
  46027. return;
  46028. }
  46029. if (this.isEndOfStream_(segmentInfo.mediaIndex)) {
  46030. this.endOfStream();
  46031. return;
  46032. }
  46033. if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
  46034. return;
  46035. } // We will need to change timestampOffset of the sourceBuffer if:
  46036. // - The segment.timeline !== this.currentTimeline
  46037. // (we are crossing a discontinuity somehow)
  46038. // - The "timestampOffset" for the start of this segment is less than
  46039. // the currently set timestampOffset
  46040. // Also, clear captions if we are crossing a discontinuity boundary
  46041. // Previously, we changed the timestampOffset if the start of this segment
  46042. // is less than the currently set timestampOffset but this isn't wanted
  46043. // as it can produce bad behavior, especially around long running
  46044. // live streams
  46045. if (segmentInfo.timeline !== this.currentTimeline_) {
  46046. this.syncController_.reset();
  46047. segmentInfo.timestampOffset = segmentInfo.startOfSegment;
  46048. if (this.captionParser_) {
  46049. this.captionParser_.clearAllCaptions();
  46050. }
  46051. }
  46052. this.loadSegment_(segmentInfo);
  46053. }
  46054. /**
  46055. * Determines if this segment loader is at the end of it's stream.
  46056. *
  46057. * @param {Number} mediaIndex the index of segment we last appended
  46058. * @param {Object} [playlist=this.playlist_] a media playlist object
  46059. * @returns {Boolean} true if at end of stream, false otherwise.
  46060. */
  46061. }, {
  46062. key: 'isEndOfStream_',
  46063. value: function isEndOfStream_(mediaIndex) {
  46064. var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_;
  46065. return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating();
  46066. }
  46067. /**
  46068. * Determines what segment request should be made, given current playback
  46069. * state.
  46070. *
  46071. * @param {TimeRanges} buffered - the state of the buffer
  46072. * @param {Object} playlist - the playlist object to fetch segments from
  46073. * @param {Number} mediaIndex - the previous mediaIndex fetched or null
  46074. * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
  46075. * @param {Number} currentTime - the playback position in seconds
  46076. * @param {Object} syncPoint - a segment info object that describes the
  46077. * @returns {Object} a segment request object that describes the segment to load
  46078. */
  46079. }, {
  46080. key: 'checkBuffer_',
  46081. value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
  46082. var lastBufferedEnd = 0;
  46083. var startOfSegment = void 0;
  46084. if (buffered.length) {
  46085. lastBufferedEnd = buffered.end(buffered.length - 1);
  46086. }
  46087. var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
  46088. if (!playlist.segments.length) {
  46089. return null;
  46090. } // if there is plenty of content buffered, and the video has
  46091. // been played before relax for awhile
  46092. if (bufferedTime >= this.goalBufferLength_()) {
  46093. return null;
  46094. } // if the video has not yet played once, and we already have
  46095. // one segment downloaded do nothing
  46096. if (!hasPlayed && bufferedTime >= 1) {
  46097. return null;
  46098. } // When the syncPoint is null, there is no way of determining a good
  46099. // conservative segment index to fetch from
  46100. // The best thing to do here is to get the kind of sync-point data by
  46101. // making a request
  46102. if (syncPoint === null) {
  46103. mediaIndex = this.getSyncSegmentCandidate_(playlist);
  46104. return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
  46105. } // Under normal playback conditions fetching is a simple walk forward
  46106. if (mediaIndex !== null) {
  46107. var segment = playlist.segments[mediaIndex];
  46108. if (segment && segment.end) {
  46109. startOfSegment = segment.end;
  46110. } else {
  46111. startOfSegment = lastBufferedEnd;
  46112. }
  46113. return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
  46114. } // There is a sync-point but the lack of a mediaIndex indicates that
  46115. // we need to make a good conservative guess about which segment to
  46116. // fetch
  46117. if (this.fetchAtBuffer_) {
  46118. // Find the segment containing the end of the buffer
  46119. var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
  46120. mediaIndex = mediaSourceInfo.mediaIndex;
  46121. startOfSegment = mediaSourceInfo.startTime;
  46122. } else {
  46123. // Find the segment containing currentTime
  46124. var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
  46125. mediaIndex = _mediaSourceInfo.mediaIndex;
  46126. startOfSegment = _mediaSourceInfo.startTime;
  46127. }
  46128. return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
  46129. }
  46130. /**
  46131. * The segment loader has no recourse except to fetch a segment in the
  46132. * current playlist and use the internal timestamps in that segment to
  46133. * generate a syncPoint. This function returns a good candidate index
  46134. * for that process.
  46135. *
  46136. * @param {Object} playlist - the playlist object to look for a
  46137. * @returns {Number} An index of a segment from the playlist to load
  46138. */
  46139. }, {
  46140. key: 'getSyncSegmentCandidate_',
  46141. value: function getSyncSegmentCandidate_(playlist) {
  46142. var _this2 = this;
  46143. if (this.currentTimeline_ === -1) {
  46144. return 0;
  46145. }
  46146. var segmentIndexArray = playlist.segments.map(function (s, i) {
  46147. return {
  46148. timeline: s.timeline,
  46149. segmentIndex: i
  46150. };
  46151. }).filter(function (s) {
  46152. return s.timeline === _this2.currentTimeline_;
  46153. });
  46154. if (segmentIndexArray.length) {
  46155. return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
  46156. }
  46157. return Math.max(playlist.segments.length - 1, 0);
  46158. }
  46159. }, {
  46160. key: 'generateSegmentInfo_',
  46161. value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
  46162. if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
  46163. return null;
  46164. }
  46165. var segment = playlist.segments[mediaIndex];
  46166. return {
  46167. requestId: 'segment-loader-' + Math.random(),
  46168. // resolve the segment URL relative to the playlist
  46169. uri: segment.resolvedUri,
  46170. // the segment's mediaIndex at the time it was requested
  46171. mediaIndex: mediaIndex,
  46172. // whether or not to update the SegmentLoader's state with this
  46173. // segment's mediaIndex
  46174. isSyncRequest: isSyncRequest,
  46175. startOfSegment: startOfSegment,
  46176. // the segment's playlist
  46177. playlist: playlist,
  46178. // unencrypted bytes of the segment
  46179. bytes: null,
  46180. // when a key is defined for this segment, the encrypted bytes
  46181. encryptedBytes: null,
  46182. // The target timestampOffset for this segment when we append it
  46183. // to the source buffer
  46184. timestampOffset: null,
  46185. // The timeline that the segment is in
  46186. timeline: segment.timeline,
  46187. // The expected duration of the segment in seconds
  46188. duration: segment.duration,
  46189. // retain the segment in case the playlist updates while doing an async process
  46190. segment: segment
  46191. };
  46192. }
  46193. /**
  46194. * Determines if the network has enough bandwidth to complete the current segment
  46195. * request in a timely manner. If not, the request will be aborted early and bandwidth
  46196. * updated to trigger a playlist switch.
  46197. *
  46198. * @param {Object} stats
  46199. * Object containing stats about the request timing and size
  46200. * @return {Boolean} True if the request was aborted, false otherwise
  46201. * @private
  46202. */
  46203. }, {
  46204. key: 'abortRequestEarly_',
  46205. value: function abortRequestEarly_(stats) {
  46206. if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
  46207. // TODO: Replace using timeout with a boolean indicating whether this playlist is
  46208. // the lowestEnabledRendition.
  46209. !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
  46210. !this.playlist_.attributes.BANDWIDTH) {
  46211. return false;
  46212. } // Wait at least 1 second since the first byte of data has been received before
  46213. // using the calculated bandwidth from the progress event to allow the bitrate
  46214. // to stabilize
  46215. if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
  46216. return false;
  46217. }
  46218. var currentTime = this.currentTime_();
  46219. var measuredBandwidth = stats.bandwidth;
  46220. var segmentDuration = this.pendingSegment_.duration;
  46221. var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
  46222. // if we are only left with less than 1 second when the request completes.
  46223. // A negative timeUntilRebuffering indicates we are already rebuffering
  46224. var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
  46225. // is larger than the estimated time until the player runs out of forward buffer
  46226. if (requestTimeRemaining <= timeUntilRebuffer$$1) {
  46227. return false;
  46228. }
  46229. var switchCandidate = minRebufferMaxBandwidthSelector({
  46230. master: this.hls_.playlists.master,
  46231. currentTime: currentTime,
  46232. bandwidth: measuredBandwidth,
  46233. duration: this.duration_(),
  46234. segmentDuration: segmentDuration,
  46235. timeUntilRebuffer: timeUntilRebuffer$$1,
  46236. currentTimeline: this.currentTimeline_,
  46237. syncController: this.syncController_
  46238. });
  46239. if (!switchCandidate) {
  46240. return;
  46241. }
  46242. var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
  46243. var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
  46244. var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
  46245. // potential round trip time of the new request so that we are not too aggressive
  46246. // with switching to a playlist that might save us a fraction of a second.
  46247. if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
  46248. minimumTimeSaving = 1;
  46249. }
  46250. if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
  46251. return false;
  46252. } // set the bandwidth to that of the desired playlist being sure to scale by
  46253. // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
  46254. // don't trigger a bandwidthupdate as the bandwidth is artifial
  46255. this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
  46256. this.abort();
  46257. this.trigger('earlyabort');
  46258. return true;
  46259. }
  46260. /**
  46261. * XHR `progress` event handler
  46262. *
  46263. * @param {Event}
  46264. * The XHR `progress` event
  46265. * @param {Object} simpleSegment
  46266. * A simplified segment object copy
  46267. * @private
  46268. */
  46269. }, {
  46270. key: 'handleProgress_',
  46271. value: function handleProgress_(event, simpleSegment) {
  46272. if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
  46273. return;
  46274. }
  46275. this.trigger('progress');
  46276. }
  46277. /**
  46278. * load a specific segment from a request into the buffer
  46279. *
  46280. * @private
  46281. */
  46282. }, {
  46283. key: 'loadSegment_',
  46284. value: function loadSegment_(segmentInfo) {
  46285. this.state = 'WAITING';
  46286. this.pendingSegment_ = segmentInfo;
  46287. this.trimBackBuffer_(segmentInfo);
  46288. segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback
  46289. this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
  46290. }
  46291. /**
  46292. * trim the back buffer so that we don't have too much data
  46293. * in the source buffer
  46294. *
  46295. * @private
  46296. *
  46297. * @param {Object} segmentInfo - the current segment
  46298. */
  46299. }, {
  46300. key: 'trimBackBuffer_',
  46301. value: function trimBackBuffer_(segmentInfo) {
  46302. var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
  46303. // buffer and a very conservative "garbage collector"
  46304. // We manually clear out the old buffer to ensure
  46305. // we don't trigger the QuotaExceeded error
  46306. // on the source buffer during subsequent appends
  46307. if (removeToTime > 0) {
  46308. this.remove(0, removeToTime);
  46309. }
  46310. }
  46311. /**
  46312. * created a simplified copy of the segment object with just the
  46313. * information necessary to perform the XHR and decryption
  46314. *
  46315. * @private
  46316. *
  46317. * @param {Object} segmentInfo - the current segment
  46318. * @returns {Object} a simplified segment object copy
  46319. */
  46320. }, {
  46321. key: 'createSimplifiedSegmentObj_',
  46322. value: function createSimplifiedSegmentObj_(segmentInfo) {
  46323. var segment = segmentInfo.segment;
  46324. var simpleSegment = {
  46325. resolvedUri: segment.resolvedUri,
  46326. byterange: segment.byterange,
  46327. requestId: segmentInfo.requestId
  46328. };
  46329. if (segment.key) {
  46330. // if the media sequence is greater than 2^32, the IV will be incorrect
  46331. // assuming 10s segments, that would be about 1300 years
  46332. var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
  46333. simpleSegment.key = this.segmentKey(segment.key);
  46334. simpleSegment.key.iv = iv;
  46335. }
  46336. if (segment.map) {
  46337. simpleSegment.map = this.initSegment(segment.map);
  46338. }
  46339. return simpleSegment;
  46340. }
  46341. /**
  46342. * Handle the callback from the segmentRequest function and set the
  46343. * associated SegmentLoader state and errors if necessary
  46344. *
  46345. * @private
  46346. */
  46347. }, {
  46348. key: 'segmentRequestFinished_',
  46349. value: function segmentRequestFinished_(error, simpleSegment) {
  46350. // every request counts as a media request even if it has been aborted
  46351. // or canceled due to a timeout
  46352. this.mediaRequests += 1;
  46353. if (simpleSegment.stats) {
  46354. this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
  46355. this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
  46356. } // The request was aborted and the SegmentLoader has already been reset
  46357. if (!this.pendingSegment_) {
  46358. this.mediaRequestsAborted += 1;
  46359. return;
  46360. } // the request was aborted and the SegmentLoader has already started
  46361. // another request. this can happen when the timeout for an aborted
  46362. // request triggers due to a limitation in the XHR library
  46363. // do not count this as any sort of request or we risk double-counting
  46364. if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
  46365. return;
  46366. } // an error occurred from the active pendingSegment_ so reset everything
  46367. if (error) {
  46368. this.pendingSegment_ = null;
  46369. this.state = 'READY'; // the requests were aborted just record the aborted stat and exit
  46370. // this is not a true error condition and nothing corrective needs
  46371. // to be done
  46372. if (error.code === REQUEST_ERRORS.ABORTED) {
  46373. this.mediaRequestsAborted += 1;
  46374. return;
  46375. }
  46376. this.pause(); // the error is really just that at least one of the requests timed-out
  46377. // set the bandwidth to a very low value and trigger an ABR switch to
  46378. // take emergency action
  46379. if (error.code === REQUEST_ERRORS.TIMEOUT) {
  46380. this.mediaRequestsTimedout += 1;
  46381. this.bandwidth = 1;
  46382. this.roundTrip = NaN;
  46383. this.trigger('bandwidthupdate');
  46384. return;
  46385. } // if control-flow has arrived here, then the error is real
  46386. // emit an error event to blacklist the current playlist
  46387. this.mediaRequestsErrored += 1;
  46388. this.error(error);
  46389. this.trigger('error');
  46390. return;
  46391. } // the response was a success so set any bandwidth stats the request
  46392. // generated for ABR purposes
  46393. this.bandwidth = simpleSegment.stats.bandwidth;
  46394. this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data
  46395. // to the initSegment cache
  46396. if (simpleSegment.map) {
  46397. simpleSegment.map = this.initSegment(simpleSegment.map, true);
  46398. } // if this request included a segment key, save that data in the cache
  46399. if (simpleSegment.key) {
  46400. this.segmentKey(simpleSegment.key, true);
  46401. }
  46402. this.processSegmentResponse_(simpleSegment);
  46403. }
  46404. /**
  46405. * Move any important data from the simplified segment object
  46406. * back to the real segment object for future phases
  46407. *
  46408. * @private
  46409. */
  46410. }, {
  46411. key: 'processSegmentResponse_',
  46412. value: function processSegmentResponse_(simpleSegment) {
  46413. var segmentInfo = this.pendingSegment_;
  46414. segmentInfo.bytes = simpleSegment.bytes;
  46415. if (simpleSegment.map) {
  46416. segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
  46417. }
  46418. segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks
  46419. if (simpleSegment.fmp4Captions) {
  46420. createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams);
  46421. addCaptionData({
  46422. inbandTextTracks: this.inbandTextTracks_,
  46423. captionArray: simpleSegment.fmp4Captions,
  46424. // fmp4s will not have a timestamp offset
  46425. timestampOffset: 0
  46426. }); // Reset stored captions since we added parsed
  46427. // captions to a text track at this point
  46428. if (this.captionParser_) {
  46429. this.captionParser_.clearParsedCaptions();
  46430. }
  46431. }
  46432. this.handleSegment_();
  46433. }
  46434. /**
  46435. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  46436. *
  46437. * @private
  46438. */
  46439. }, {
  46440. key: 'handleSegment_',
  46441. value: function handleSegment_() {
  46442. var _this3 = this;
  46443. if (!this.pendingSegment_) {
  46444. this.state = 'READY';
  46445. return;
  46446. }
  46447. var segmentInfo = this.pendingSegment_;
  46448. var segment = segmentInfo.segment;
  46449. var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is
  46450. // dealing with. Although we're maintaining extra state, it helps to preserve the
  46451. // separation of segment loader from the actual source buffers.
  46452. if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are
  46453. // certain that all streams will provide it.
  46454. timingInfo.containsAudio || timingInfo.containsVideo)) {
  46455. this.startingMedia_ = {
  46456. containsAudio: timingInfo.containsAudio,
  46457. containsVideo: timingInfo.containsVideo
  46458. };
  46459. }
  46460. var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
  46461. if (illegalMediaSwitchError) {
  46462. this.error({
  46463. message: illegalMediaSwitchError,
  46464. blacklistDuration: Infinity
  46465. });
  46466. this.trigger('error');
  46467. return;
  46468. }
  46469. if (segmentInfo.isSyncRequest) {
  46470. this.trigger('syncinfoupdate');
  46471. this.pendingSegment_ = null;
  46472. this.state = 'READY';
  46473. return;
  46474. }
  46475. if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
  46476. this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities)
  46477. this.trigger('timestampoffset');
  46478. }
  46479. var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
  46480. if (timelineMapping !== null) {
  46481. this.trigger({
  46482. type: 'segmenttimemapping',
  46483. mapping: timelineMapping
  46484. });
  46485. }
  46486. this.state = 'APPENDING'; // if the media initialization segment is changing, append it
  46487. // before the content segment
  46488. if (segment.map) {
  46489. var initId = initSegmentId(segment.map);
  46490. if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
  46491. var initSegment = this.initSegment(segment.map);
  46492. this.sourceUpdater_.appendBuffer({
  46493. bytes: initSegment.bytes
  46494. }, function () {
  46495. _this3.activeInitSegmentId_ = initId;
  46496. });
  46497. }
  46498. }
  46499. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  46500. if (typeof segment.start === 'number' && typeof segment.end === 'number') {
  46501. this.mediaSecondsLoaded += segment.end - segment.start;
  46502. } else {
  46503. this.mediaSecondsLoaded += segment.duration;
  46504. }
  46505. this.logger_(segmentInfoString(segmentInfo));
  46506. this.sourceUpdater_.appendBuffer({
  46507. bytes: segmentInfo.bytes,
  46508. videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId)
  46509. }, this.handleUpdateEnd_.bind(this));
  46510. }
  46511. }, {
  46512. key: 'handleVideoSegmentTimingInfo_',
  46513. value: function handleVideoSegmentTimingInfo_(requestId, event) {
  46514. if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
  46515. return;
  46516. }
  46517. var segment = this.pendingSegment_.segment;
  46518. if (!segment.videoTimingInfo) {
  46519. segment.videoTimingInfo = {};
  46520. }
  46521. segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0;
  46522. segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation;
  46523. segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging
  46524. segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime;
  46525. }
  46526. /**
  46527. * callback to run when appendBuffer is finished. detects if we are
  46528. * in a good state to do things with the data we got, or if we need
  46529. * to wait for more
  46530. *
  46531. * @private
  46532. */
  46533. }, {
  46534. key: 'handleUpdateEnd_',
  46535. value: function handleUpdateEnd_() {
  46536. if (!this.pendingSegment_) {
  46537. this.state = 'READY';
  46538. if (!this.paused()) {
  46539. this.monitorBuffer_();
  46540. }
  46541. return;
  46542. }
  46543. var segmentInfo = this.pendingSegment_;
  46544. var segment = segmentInfo.segment;
  46545. var isWalkingForward = this.mediaIndex !== null;
  46546. this.pendingSegment_ = null;
  46547. this.recordThroughput_(segmentInfo);
  46548. this.addSegmentMetadataCue_(segmentInfo);
  46549. this.state = 'READY';
  46550. this.mediaIndex = segmentInfo.mediaIndex;
  46551. this.fetchAtBuffer_ = true;
  46552. this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
  46553. // the following conditional otherwise it may consider this a bad "guess"
  46554. // and attempt to resync when the post-update seekable window and live
  46555. // point would mean that this was the perfect segment to fetch
  46556. this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before
  46557. // the currentTime_ that means that our conservative guess was too conservative.
  46558. // In that case, reset the loader state so that we try to use any information gained
  46559. // from the previous request to create a new, more accurate, sync-point.
  46560. if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
  46561. this.resetEverything();
  46562. return;
  46563. } // Don't do a rendition switch unless we have enough time to get a sync segment
  46564. // and conservatively guess
  46565. if (isWalkingForward) {
  46566. this.trigger('bandwidthupdate');
  46567. }
  46568. this.trigger('progress'); // any time an update finishes and the last segment is in the
  46569. // buffer, end the stream. this ensures the "ended" event will
  46570. // fire if playback reaches that point.
  46571. if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) {
  46572. this.endOfStream();
  46573. }
  46574. if (!this.paused()) {
  46575. this.monitorBuffer_();
  46576. }
  46577. }
  46578. /**
  46579. * Records the current throughput of the decrypt, transmux, and append
  46580. * portion of the semgment pipeline. `throughput.rate` is a the cumulative
  46581. * moving average of the throughput. `throughput.count` is the number of
  46582. * data points in the average.
  46583. *
  46584. * @private
  46585. * @param {Object} segmentInfo the object returned by loadSegment
  46586. */
  46587. }, {
  46588. key: 'recordThroughput_',
  46589. value: function recordThroughput_(segmentInfo) {
  46590. var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
  46591. // by zero in the case where the throughput is ridiculously high
  46592. var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
  46593. var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
  46594. // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
  46595. this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
  46596. }
  46597. /**
  46598. * Adds a cue to the segment-metadata track with some metadata information about the
  46599. * segment
  46600. *
  46601. * @private
  46602. * @param {Object} segmentInfo
  46603. * the object returned by loadSegment
  46604. * @method addSegmentMetadataCue_
  46605. */
  46606. }, {
  46607. key: 'addSegmentMetadataCue_',
  46608. value: function addSegmentMetadataCue_(segmentInfo) {
  46609. if (!this.segmentMetadataTrack_) {
  46610. return;
  46611. }
  46612. var segment = segmentInfo.segment;
  46613. var start = segment.start;
  46614. var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
  46615. if (!finite(start) || !finite(end)) {
  46616. return;
  46617. }
  46618. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  46619. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  46620. var value = {
  46621. custom: segment.custom,
  46622. dateTimeObject: segment.dateTimeObject,
  46623. dateTimeString: segment.dateTimeString,
  46624. bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
  46625. resolution: segmentInfo.playlist.attributes.RESOLUTION,
  46626. codecs: segmentInfo.playlist.attributes.CODECS,
  46627. byteLength: segmentInfo.byteLength,
  46628. uri: segmentInfo.uri,
  46629. timeline: segmentInfo.timeline,
  46630. playlist: segmentInfo.playlist.uri,
  46631. start: start,
  46632. end: end
  46633. };
  46634. var data = JSON.stringify(value);
  46635. var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
  46636. // the differences of WebKitDataCue in safari and VTTCue in other browsers
  46637. cue.value = value;
  46638. this.segmentMetadataTrack_.addCue(cue);
  46639. }
  46640. }]);
  46641. return SegmentLoader;
  46642. }(videojs$1.EventTarget);
  46643. var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
  46644. return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
  46645. };
  46646. /**
  46647. * @file vtt-segment-loader.js
  46648. */
  46649. var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char2) {
  46650. return _char2.charCodeAt(0);
  46651. }));
  46652. /**
  46653. * An object that manages segment loading and appending.
  46654. *
  46655. * @class VTTSegmentLoader
  46656. * @param {Object} options required and optional options
  46657. * @extends videojs.EventTarget
  46658. */
  46659. var VTTSegmentLoader = function (_SegmentLoader) {
  46660. inherits$1(VTTSegmentLoader, _SegmentLoader);
  46661. function VTTSegmentLoader(settings) {
  46662. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  46663. classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error;
  46664. // however, VTTSegmentLoader has no need of a media source, so delete the reference
  46665. var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
  46666. _this.mediaSource_ = null;
  46667. _this.subtitlesTrack_ = null;
  46668. return _this;
  46669. }
  46670. /**
  46671. * Indicates which time ranges are buffered
  46672. *
  46673. * @return {TimeRange}
  46674. * TimeRange object representing the current buffered ranges
  46675. */
  46676. createClass$1(VTTSegmentLoader, [{
  46677. key: 'buffered_',
  46678. value: function buffered_() {
  46679. if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
  46680. return videojs$1.createTimeRanges();
  46681. }
  46682. var cues = this.subtitlesTrack_.cues;
  46683. var start = cues[0].startTime;
  46684. var end = cues[cues.length - 1].startTime;
  46685. return videojs$1.createTimeRanges([[start, end]]);
  46686. }
  46687. /**
  46688. * Gets and sets init segment for the provided map
  46689. *
  46690. * @param {Object} map
  46691. * The map object representing the init segment to get or set
  46692. * @param {Boolean=} set
  46693. * If true, the init segment for the provided map should be saved
  46694. * @return {Object}
  46695. * map object for desired init segment
  46696. */
  46697. }, {
  46698. key: 'initSegment',
  46699. value: function initSegment(map) {
  46700. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  46701. if (!map) {
  46702. return null;
  46703. }
  46704. var id = initSegmentId(map);
  46705. var storedMap = this.initSegments_[id];
  46706. if (set$$1 && !storedMap && map.bytes) {
  46707. // append WebVTT line terminators to the media initialization segment if it exists
  46708. // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
  46709. // requires two or more WebVTT line terminators between the WebVTT header and the
  46710. // rest of the file
  46711. var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
  46712. var combinedSegment = new Uint8Array(combinedByteLength);
  46713. combinedSegment.set(map.bytes);
  46714. combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
  46715. this.initSegments_[id] = storedMap = {
  46716. resolvedUri: map.resolvedUri,
  46717. byterange: map.byterange,
  46718. bytes: combinedSegment
  46719. };
  46720. }
  46721. return storedMap || map;
  46722. }
  46723. /**
  46724. * Returns true if all configuration required for loading is present, otherwise false.
  46725. *
  46726. * @return {Boolean} True if the all configuration is ready for loading
  46727. * @private
  46728. */
  46729. }, {
  46730. key: 'couldBeginLoading_',
  46731. value: function couldBeginLoading_() {
  46732. return this.playlist_ && this.subtitlesTrack_ && !this.paused();
  46733. }
  46734. /**
  46735. * Once all the starting parameters have been specified, begin
  46736. * operation. This method should only be invoked from the INIT
  46737. * state.
  46738. *
  46739. * @private
  46740. */
  46741. }, {
  46742. key: 'init_',
  46743. value: function init_() {
  46744. this.state = 'READY';
  46745. this.resetEverything();
  46746. return this.monitorBuffer_();
  46747. }
  46748. /**
  46749. * Set a subtitle track on the segment loader to add subtitles to
  46750. *
  46751. * @param {TextTrack=} track
  46752. * The text track to add loaded subtitles to
  46753. * @return {TextTrack}
  46754. * Returns the subtitles track
  46755. */
  46756. }, {
  46757. key: 'track',
  46758. value: function track(_track) {
  46759. if (typeof _track === 'undefined') {
  46760. return this.subtitlesTrack_;
  46761. }
  46762. this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
  46763. // buffering now
  46764. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  46765. this.init_();
  46766. }
  46767. return this.subtitlesTrack_;
  46768. }
  46769. /**
  46770. * Remove any data in the source buffer between start and end times
  46771. * @param {Number} start - the start time of the region to remove from the buffer
  46772. * @param {Number} end - the end time of the region to remove from the buffer
  46773. */
  46774. }, {
  46775. key: 'remove',
  46776. value: function remove(start, end) {
  46777. removeCuesFromTrack(start, end, this.subtitlesTrack_);
  46778. }
  46779. /**
  46780. * fill the buffer with segements unless the sourceBuffers are
  46781. * currently updating
  46782. *
  46783. * Note: this function should only ever be called by monitorBuffer_
  46784. * and never directly
  46785. *
  46786. * @private
  46787. */
  46788. }, {
  46789. key: 'fillBuffer_',
  46790. value: function fillBuffer_() {
  46791. var _this2 = this;
  46792. if (!this.syncPoint_) {
  46793. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  46794. } // see if we need to begin loading immediately
  46795. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  46796. segmentInfo = this.skipEmptySegments_(segmentInfo);
  46797. if (!segmentInfo) {
  46798. return;
  46799. }
  46800. if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
  46801. // We don't have the timestamp offset that we need to sync subtitles.
  46802. // Rerun on a timestamp offset or user interaction.
  46803. var checkTimestampOffset = function checkTimestampOffset() {
  46804. _this2.state = 'READY';
  46805. if (!_this2.paused()) {
  46806. // if not paused, queue a buffer check as soon as possible
  46807. _this2.monitorBuffer_();
  46808. }
  46809. };
  46810. this.syncController_.one('timestampoffset', checkTimestampOffset);
  46811. this.state = 'WAITING_ON_TIMELINE';
  46812. return;
  46813. }
  46814. this.loadSegment_(segmentInfo);
  46815. }
  46816. /**
  46817. * Prevents the segment loader from requesting segments we know contain no subtitles
  46818. * by walking forward until we find the next segment that we don't know whether it is
  46819. * empty or not.
  46820. *
  46821. * @param {Object} segmentInfo
  46822. * a segment info object that describes the current segment
  46823. * @return {Object}
  46824. * a segment info object that describes the current segment
  46825. */
  46826. }, {
  46827. key: 'skipEmptySegments_',
  46828. value: function skipEmptySegments_(segmentInfo) {
  46829. while (segmentInfo && segmentInfo.segment.empty) {
  46830. segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
  46831. }
  46832. return segmentInfo;
  46833. }
  46834. /**
  46835. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  46836. *
  46837. * @private
  46838. */
  46839. }, {
  46840. key: 'handleSegment_',
  46841. value: function handleSegment_() {
  46842. var _this3 = this;
  46843. if (!this.pendingSegment_ || !this.subtitlesTrack_) {
  46844. this.state = 'READY';
  46845. return;
  46846. }
  46847. this.state = 'APPENDING';
  46848. var segmentInfo = this.pendingSegment_;
  46849. var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  46850. if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
  46851. var loadHandler = void 0;
  46852. var errorHandler = function errorHandler() {
  46853. _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
  46854. _this3.error({
  46855. message: 'Error loading vtt.js'
  46856. });
  46857. _this3.state = 'READY';
  46858. _this3.pause();
  46859. _this3.trigger('error');
  46860. };
  46861. loadHandler = function loadHandler() {
  46862. _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler);
  46863. _this3.handleSegment_();
  46864. };
  46865. this.state = 'WAITING_ON_VTTJS';
  46866. this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
  46867. this.subtitlesTrack_.tech_.one('vttjserror', errorHandler);
  46868. return;
  46869. }
  46870. segment.requested = true;
  46871. try {
  46872. this.parseVTTCues_(segmentInfo);
  46873. } catch (e) {
  46874. this.error({
  46875. message: e.message
  46876. });
  46877. this.state = 'READY';
  46878. this.pause();
  46879. return this.trigger('error');
  46880. }
  46881. this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
  46882. if (segmentInfo.isSyncRequest) {
  46883. this.trigger('syncinfoupdate');
  46884. this.pendingSegment_ = null;
  46885. this.state = 'READY';
  46886. return;
  46887. }
  46888. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  46889. this.mediaSecondsLoaded += segment.duration;
  46890. if (segmentInfo.cues.length) {
  46891. // remove any overlapping cues to prevent doubling
  46892. this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
  46893. }
  46894. segmentInfo.cues.forEach(function (cue) {
  46895. _this3.subtitlesTrack_.addCue(cue);
  46896. });
  46897. this.handleUpdateEnd_();
  46898. }
  46899. /**
  46900. * Uses the WebVTT parser to parse the segment response
  46901. *
  46902. * @param {Object} segmentInfo
  46903. * a segment info object that describes the current segment
  46904. * @private
  46905. */
  46906. }, {
  46907. key: 'parseVTTCues_',
  46908. value: function parseVTTCues_(segmentInfo) {
  46909. var decoder = void 0;
  46910. var decodeBytesToString = false;
  46911. if (typeof window$1.TextDecoder === 'function') {
  46912. decoder = new window$1.TextDecoder('utf8');
  46913. } else {
  46914. decoder = window$1.WebVTT.StringDecoder();
  46915. decodeBytesToString = true;
  46916. }
  46917. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
  46918. segmentInfo.cues = [];
  46919. segmentInfo.timestampmap = {
  46920. MPEGTS: 0,
  46921. LOCAL: 0
  46922. };
  46923. parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
  46924. parser.ontimestampmap = function (map) {
  46925. return segmentInfo.timestampmap = map;
  46926. };
  46927. parser.onparsingerror = function (error) {
  46928. videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
  46929. };
  46930. if (segmentInfo.segment.map) {
  46931. var mapData = segmentInfo.segment.map.bytes;
  46932. if (decodeBytesToString) {
  46933. mapData = uint8ToUtf8(mapData);
  46934. }
  46935. parser.parse(mapData);
  46936. }
  46937. var segmentData = segmentInfo.bytes;
  46938. if (decodeBytesToString) {
  46939. segmentData = uint8ToUtf8(segmentData);
  46940. }
  46941. parser.parse(segmentData);
  46942. parser.flush();
  46943. }
  46944. /**
  46945. * Updates the start and end times of any cues parsed by the WebVTT parser using
  46946. * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
  46947. * from the SyncController
  46948. *
  46949. * @param {Object} segmentInfo
  46950. * a segment info object that describes the current segment
  46951. * @param {Object} mappingObj
  46952. * object containing a mapping from TS to media time
  46953. * @param {Object} playlist
  46954. * the playlist object containing the segment
  46955. * @private
  46956. */
  46957. }, {
  46958. key: 'updateTimeMapping_',
  46959. value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
  46960. var segment = segmentInfo.segment;
  46961. if (!mappingObj) {
  46962. // If the sync controller does not have a mapping of TS to Media Time for the
  46963. // timeline, then we don't have enough information to update the cue
  46964. // start/end times
  46965. return;
  46966. }
  46967. if (!segmentInfo.cues.length) {
  46968. // If there are no cues, we also do not have enough information to figure out
  46969. // segment timing. Mark that the segment contains no cues so we don't re-request
  46970. // an empty segment.
  46971. segment.empty = true;
  46972. return;
  46973. }
  46974. var timestampmap = segmentInfo.timestampmap;
  46975. var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
  46976. segmentInfo.cues.forEach(function (cue) {
  46977. // First convert cue time to TS time using the timestamp-map provided within the vtt
  46978. cue.startTime += diff;
  46979. cue.endTime += diff;
  46980. });
  46981. if (!playlist.syncInfo) {
  46982. var firstStart = segmentInfo.cues[0].startTime;
  46983. var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
  46984. playlist.syncInfo = {
  46985. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  46986. time: Math.min(firstStart, lastStart - segment.duration)
  46987. };
  46988. }
  46989. }
  46990. }]);
  46991. return VTTSegmentLoader;
  46992. }(SegmentLoader);
  46993. /**
  46994. * @file ad-cue-tags.js
  46995. */
  46996. /**
  46997. * Searches for an ad cue that overlaps with the given mediaTime
  46998. */
  46999. var findAdCue = function findAdCue(track, mediaTime) {
  47000. var cues = track.cues;
  47001. for (var i = 0; i < cues.length; i++) {
  47002. var cue = cues[i];
  47003. if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
  47004. return cue;
  47005. }
  47006. }
  47007. return null;
  47008. };
  47009. var updateAdCues = function updateAdCues(media, track) {
  47010. var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  47011. if (!media.segments) {
  47012. return;
  47013. }
  47014. var mediaTime = offset;
  47015. var cue = void 0;
  47016. for (var i = 0; i < media.segments.length; i++) {
  47017. var segment = media.segments[i];
  47018. if (!cue) {
  47019. // Since the cues will span for at least the segment duration, adding a fudge
  47020. // factor of half segment duration will prevent duplicate cues from being
  47021. // created when timing info is not exact (e.g. cue start time initialized
  47022. // at 10.006677, but next call mediaTime is 10.003332 )
  47023. cue = findAdCue(track, mediaTime + segment.duration / 2);
  47024. }
  47025. if (cue) {
  47026. if ('cueIn' in segment) {
  47027. // Found a CUE-IN so end the cue
  47028. cue.endTime = mediaTime;
  47029. cue.adEndTime = mediaTime;
  47030. mediaTime += segment.duration;
  47031. cue = null;
  47032. continue;
  47033. }
  47034. if (mediaTime < cue.endTime) {
  47035. // Already processed this mediaTime for this cue
  47036. mediaTime += segment.duration;
  47037. continue;
  47038. } // otherwise extend cue until a CUE-IN is found
  47039. cue.endTime += segment.duration;
  47040. } else {
  47041. if ('cueOut' in segment) {
  47042. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
  47043. cue.adStartTime = mediaTime; // Assumes tag format to be
  47044. // #EXT-X-CUE-OUT:30
  47045. cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
  47046. track.addCue(cue);
  47047. }
  47048. if ('cueOutCont' in segment) {
  47049. // Entered into the middle of an ad cue
  47050. var adOffset = void 0;
  47051. var adTotal = void 0; // Assumes tag formate to be
  47052. // #EXT-X-CUE-OUT-CONT:10/30
  47053. var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
  47054. var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2);
  47055. adOffset = _segment$cueOutCont$s2[0];
  47056. adTotal = _segment$cueOutCont$s2[1];
  47057. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
  47058. cue.adStartTime = mediaTime - adOffset;
  47059. cue.adEndTime = cue.adStartTime + adTotal;
  47060. track.addCue(cue);
  47061. }
  47062. }
  47063. mediaTime += segment.duration;
  47064. }
  47065. };
  47066. /**
  47067. * @file sync-controller.js
  47068. */
  47069. var tsprobe = tsInspector.inspect;
  47070. var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
  47071. // the equivalence display-time 0 === segment-index 0
  47072. {
  47073. name: 'VOD',
  47074. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  47075. if (duration$$1 !== Infinity) {
  47076. var syncPoint = {
  47077. time: 0,
  47078. segmentIndex: 0
  47079. };
  47080. return syncPoint;
  47081. }
  47082. return null;
  47083. }
  47084. }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
  47085. {
  47086. name: 'ProgramDateTime',
  47087. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  47088. if (!syncController.datetimeToDisplayTime) {
  47089. return null;
  47090. }
  47091. var segments = playlist.segments || [];
  47092. var syncPoint = null;
  47093. var lastDistance = null;
  47094. currentTime = currentTime || 0;
  47095. for (var i = 0; i < segments.length; i++) {
  47096. var segment = segments[i];
  47097. if (segment.dateTimeObject) {
  47098. var segmentTime = segment.dateTimeObject.getTime() / 1000;
  47099. var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
  47100. var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, or if distance is 0, we have passed
  47101. // currentTime and can stop looking for better candidates
  47102. if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
  47103. break;
  47104. }
  47105. lastDistance = distance;
  47106. syncPoint = {
  47107. time: segmentStart,
  47108. segmentIndex: i
  47109. };
  47110. }
  47111. }
  47112. return syncPoint;
  47113. }
  47114. }, // Stategy "Segment": We have a known time mapping for a timeline and a
  47115. // segment in the current timeline with timing data
  47116. {
  47117. name: 'Segment',
  47118. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  47119. var segments = playlist.segments || [];
  47120. var syncPoint = null;
  47121. var lastDistance = null;
  47122. currentTime = currentTime || 0;
  47123. for (var i = 0; i < segments.length; i++) {
  47124. var segment = segments[i];
  47125. if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
  47126. var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed
  47127. // currentTime and can stop looking for better candidates
  47128. if (lastDistance !== null && lastDistance < distance) {
  47129. break;
  47130. }
  47131. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  47132. lastDistance = distance;
  47133. syncPoint = {
  47134. time: segment.start,
  47135. segmentIndex: i
  47136. };
  47137. }
  47138. }
  47139. }
  47140. return syncPoint;
  47141. }
  47142. }, // Stategy "Discontinuity": We have a discontinuity with a known
  47143. // display-time
  47144. {
  47145. name: 'Discontinuity',
  47146. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  47147. var syncPoint = null;
  47148. currentTime = currentTime || 0;
  47149. if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  47150. var lastDistance = null;
  47151. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  47152. var segmentIndex = playlist.discontinuityStarts[i];
  47153. var discontinuity = playlist.discontinuitySequence + i + 1;
  47154. var discontinuitySync = syncController.discontinuities[discontinuity];
  47155. if (discontinuitySync) {
  47156. var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
  47157. // currentTime and can stop looking for better candidates
  47158. if (lastDistance !== null && lastDistance < distance) {
  47159. break;
  47160. }
  47161. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  47162. lastDistance = distance;
  47163. syncPoint = {
  47164. time: discontinuitySync.time,
  47165. segmentIndex: segmentIndex
  47166. };
  47167. }
  47168. }
  47169. }
  47170. }
  47171. return syncPoint;
  47172. }
  47173. }, // Stategy "Playlist": We have a playlist with a known mapping of
  47174. // segment index to display time
  47175. {
  47176. name: 'Playlist',
  47177. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  47178. if (playlist.syncInfo) {
  47179. var syncPoint = {
  47180. time: playlist.syncInfo.time,
  47181. segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
  47182. };
  47183. return syncPoint;
  47184. }
  47185. return null;
  47186. }
  47187. }];
  47188. var SyncController = function (_videojs$EventTarget) {
  47189. inherits$1(SyncController, _videojs$EventTarget);
  47190. function SyncController() {
  47191. classCallCheck$1(this, SyncController); // Segment Loader state variables...
  47192. // ...for synching across variants
  47193. var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
  47194. _this.inspectCache_ = undefined; // ...for synching across variants
  47195. _this.timelines = [];
  47196. _this.discontinuities = [];
  47197. _this.datetimeToDisplayTime = null;
  47198. _this.logger_ = logger('SyncController');
  47199. return _this;
  47200. }
  47201. /**
  47202. * Find a sync-point for the playlist specified
  47203. *
  47204. * A sync-point is defined as a known mapping from display-time to
  47205. * a segment-index in the current playlist.
  47206. *
  47207. * @param {Playlist} playlist
  47208. * The playlist that needs a sync-point
  47209. * @param {Number} duration
  47210. * Duration of the MediaSource (Infinite if playing a live source)
  47211. * @param {Number} currentTimeline
  47212. * The last timeline from which a segment was loaded
  47213. * @returns {Object}
  47214. * A sync-point object
  47215. */
  47216. createClass$1(SyncController, [{
  47217. key: 'getSyncPoint',
  47218. value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
  47219. var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
  47220. if (!syncPoints.length) {
  47221. // Signal that we need to attempt to get a sync-point manually
  47222. // by fetching a segment in the playlist and constructing
  47223. // a sync-point from that information
  47224. return null;
  47225. } // Now find the sync-point that is closest to the currentTime because
  47226. // that should result in the most accurate guess about which segment
  47227. // to fetch
  47228. return this.selectSyncPoint_(syncPoints, {
  47229. key: 'time',
  47230. value: currentTime
  47231. });
  47232. }
  47233. /**
  47234. * Calculate the amount of time that has expired off the playlist during playback
  47235. *
  47236. * @param {Playlist} playlist
  47237. * Playlist object to calculate expired from
  47238. * @param {Number} duration
  47239. * Duration of the MediaSource (Infinity if playling a live source)
  47240. * @returns {Number|null}
  47241. * The amount of time that has expired off the playlist during playback. Null
  47242. * if no sync-points for the playlist can be found.
  47243. */
  47244. }, {
  47245. key: 'getExpiredTime',
  47246. value: function getExpiredTime(playlist, duration$$1) {
  47247. if (!playlist || !playlist.segments) {
  47248. return null;
  47249. }
  47250. var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
  47251. if (!syncPoints.length) {
  47252. return null;
  47253. }
  47254. var syncPoint = this.selectSyncPoint_(syncPoints, {
  47255. key: 'segmentIndex',
  47256. value: 0
  47257. }); // If the sync-point is beyond the start of the playlist, we want to subtract the
  47258. // duration from index 0 to syncPoint.segmentIndex instead of adding.
  47259. if (syncPoint.segmentIndex > 0) {
  47260. syncPoint.time *= -1;
  47261. }
  47262. return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
  47263. }
  47264. /**
  47265. * Runs each sync-point strategy and returns a list of sync-points returned by the
  47266. * strategies
  47267. *
  47268. * @private
  47269. * @param {Playlist} playlist
  47270. * The playlist that needs a sync-point
  47271. * @param {Number} duration
  47272. * Duration of the MediaSource (Infinity if playing a live source)
  47273. * @param {Number} currentTimeline
  47274. * The last timeline from which a segment was loaded
  47275. * @returns {Array}
  47276. * A list of sync-point objects
  47277. */
  47278. }, {
  47279. key: 'runStrategies_',
  47280. value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
  47281. var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
  47282. for (var i = 0; i < syncPointStrategies.length; i++) {
  47283. var strategy = syncPointStrategies[i];
  47284. var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
  47285. if (syncPoint) {
  47286. syncPoint.strategy = strategy.name;
  47287. syncPoints.push({
  47288. strategy: strategy.name,
  47289. syncPoint: syncPoint
  47290. });
  47291. }
  47292. }
  47293. return syncPoints;
  47294. }
  47295. /**
  47296. * Selects the sync-point nearest the specified target
  47297. *
  47298. * @private
  47299. * @param {Array} syncPoints
  47300. * List of sync-points to select from
  47301. * @param {Object} target
  47302. * Object specifying the property and value we are targeting
  47303. * @param {String} target.key
  47304. * Specifies the property to target. Must be either 'time' or 'segmentIndex'
  47305. * @param {Number} target.value
  47306. * The value to target for the specified key.
  47307. * @returns {Object}
  47308. * The sync-point nearest the target
  47309. */
  47310. }, {
  47311. key: 'selectSyncPoint_',
  47312. value: function selectSyncPoint_(syncPoints, target) {
  47313. var bestSyncPoint = syncPoints[0].syncPoint;
  47314. var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
  47315. var bestStrategy = syncPoints[0].strategy;
  47316. for (var i = 1; i < syncPoints.length; i++) {
  47317. var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
  47318. if (newDistance < bestDistance) {
  47319. bestDistance = newDistance;
  47320. bestSyncPoint = syncPoints[i].syncPoint;
  47321. bestStrategy = syncPoints[i].strategy;
  47322. }
  47323. }
  47324. this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
  47325. return bestSyncPoint;
  47326. }
  47327. /**
  47328. * Save any meta-data present on the segments when segments leave
  47329. * the live window to the playlist to allow for synchronization at the
  47330. * playlist level later.
  47331. *
  47332. * @param {Playlist} oldPlaylist - The previous active playlist
  47333. * @param {Playlist} newPlaylist - The updated and most current playlist
  47334. */
  47335. }, {
  47336. key: 'saveExpiredSegmentInfo',
  47337. value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
  47338. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
  47339. // save that information as a possible sync-point reference in future
  47340. for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
  47341. var lastRemovedSegment = oldPlaylist.segments[i];
  47342. if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
  47343. newPlaylist.syncInfo = {
  47344. mediaSequence: oldPlaylist.mediaSequence + i,
  47345. time: lastRemovedSegment.start
  47346. };
  47347. this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
  47348. this.trigger('syncinfoupdate');
  47349. break;
  47350. }
  47351. }
  47352. }
  47353. /**
  47354. * Save the mapping from playlist's ProgramDateTime to display. This should
  47355. * only ever happen once at the start of playback.
  47356. *
  47357. * @param {Playlist} playlist - The currently active playlist
  47358. */
  47359. }, {
  47360. key: 'setDateTimeMapping',
  47361. value: function setDateTimeMapping(playlist) {
  47362. if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
  47363. var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
  47364. this.datetimeToDisplayTime = -playlistTimestamp;
  47365. }
  47366. }
  47367. /**
  47368. * Reset the state of the inspection cache when we do a rendition
  47369. * switch
  47370. */
  47371. }, {
  47372. key: 'reset',
  47373. value: function reset() {
  47374. this.inspectCache_ = undefined;
  47375. }
  47376. /**
  47377. * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
  47378. * and end of the segment in it's internal "media time". Used to generate
  47379. * mappings from that internal "media time" to the display time that is
  47380. * shown on the player.
  47381. *
  47382. * @param {SegmentInfo} segmentInfo - The current active request information
  47383. */
  47384. }, {
  47385. key: 'probeSegmentInfo',
  47386. value: function probeSegmentInfo(segmentInfo) {
  47387. var segment = segmentInfo.segment;
  47388. var playlist = segmentInfo.playlist;
  47389. var timingInfo = void 0;
  47390. if (segment.map) {
  47391. timingInfo = this.probeMp4Segment_(segmentInfo);
  47392. } else {
  47393. timingInfo = this.probeTsSegment_(segmentInfo);
  47394. }
  47395. if (timingInfo) {
  47396. if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
  47397. this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
  47398. // now with segment timing information
  47399. if (!playlist.syncInfo) {
  47400. playlist.syncInfo = {
  47401. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  47402. time: segment.start
  47403. };
  47404. }
  47405. }
  47406. }
  47407. return timingInfo;
  47408. }
  47409. /**
  47410. * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
  47411. * in it's internal "media time".
  47412. *
  47413. * @private
  47414. * @param {SegmentInfo} segmentInfo - The current active request information
  47415. * @return {object} The start and end time of the current segment in "media time"
  47416. */
  47417. }, {
  47418. key: 'probeMp4Segment_',
  47419. value: function probeMp4Segment_(segmentInfo) {
  47420. var segment = segmentInfo.segment;
  47421. var timescales = probe.timescale(segment.map.bytes);
  47422. var startTime = probe.startTime(timescales, segmentInfo.bytes);
  47423. if (segmentInfo.timestampOffset !== null) {
  47424. segmentInfo.timestampOffset -= startTime;
  47425. }
  47426. return {
  47427. start: startTime,
  47428. end: startTime + segment.duration
  47429. };
  47430. }
  47431. /**
  47432. * Probe an mpeg2-ts segment to determine the start and end of the segment
  47433. * in it's internal "media time".
  47434. *
  47435. * @private
  47436. * @param {SegmentInfo} segmentInfo - The current active request information
  47437. * @return {object} The start and end time of the current segment in "media time"
  47438. */
  47439. }, {
  47440. key: 'probeTsSegment_',
  47441. value: function probeTsSegment_(segmentInfo) {
  47442. var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
  47443. var segmentStartTime = void 0;
  47444. var segmentEndTime = void 0;
  47445. if (!timeInfo) {
  47446. return null;
  47447. }
  47448. if (timeInfo.video && timeInfo.video.length === 2) {
  47449. this.inspectCache_ = timeInfo.video[1].dts;
  47450. segmentStartTime = timeInfo.video[0].dtsTime;
  47451. segmentEndTime = timeInfo.video[1].dtsTime;
  47452. } else if (timeInfo.audio && timeInfo.audio.length === 2) {
  47453. this.inspectCache_ = timeInfo.audio[1].dts;
  47454. segmentStartTime = timeInfo.audio[0].dtsTime;
  47455. segmentEndTime = timeInfo.audio[1].dtsTime;
  47456. }
  47457. var probedInfo = {
  47458. start: segmentStartTime,
  47459. end: segmentEndTime,
  47460. containsVideo: timeInfo.video && timeInfo.video.length === 2,
  47461. containsAudio: timeInfo.audio && timeInfo.audio.length === 2
  47462. };
  47463. return probedInfo;
  47464. }
  47465. }, {
  47466. key: 'timestampOffsetForTimeline',
  47467. value: function timestampOffsetForTimeline(timeline) {
  47468. if (typeof this.timelines[timeline] === 'undefined') {
  47469. return null;
  47470. }
  47471. return this.timelines[timeline].time;
  47472. }
  47473. }, {
  47474. key: 'mappingForTimeline',
  47475. value: function mappingForTimeline(timeline) {
  47476. if (typeof this.timelines[timeline] === 'undefined') {
  47477. return null;
  47478. }
  47479. return this.timelines[timeline].mapping;
  47480. }
  47481. /**
  47482. * Use the "media time" for a segment to generate a mapping to "display time" and
  47483. * save that display time to the segment.
  47484. *
  47485. * @private
  47486. * @param {SegmentInfo} segmentInfo
  47487. * The current active request information
  47488. * @param {object} timingInfo
  47489. * The start and end time of the current segment in "media time"
  47490. * @returns {Boolean}
  47491. * Returns false if segment time mapping could not be calculated
  47492. */
  47493. }, {
  47494. key: 'calculateSegmentTimeMapping_',
  47495. value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
  47496. var segment = segmentInfo.segment;
  47497. var mappingObj = this.timelines[segmentInfo.timeline];
  47498. if (segmentInfo.timestampOffset !== null) {
  47499. mappingObj = {
  47500. time: segmentInfo.startOfSegment,
  47501. mapping: segmentInfo.startOfSegment - timingInfo.start
  47502. };
  47503. this.timelines[segmentInfo.timeline] = mappingObj;
  47504. this.trigger('timestampoffset');
  47505. this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
  47506. segment.start = segmentInfo.startOfSegment;
  47507. segment.end = timingInfo.end + mappingObj.mapping;
  47508. } else if (mappingObj) {
  47509. segment.start = timingInfo.start + mappingObj.mapping;
  47510. segment.end = timingInfo.end + mappingObj.mapping;
  47511. } else {
  47512. return false;
  47513. }
  47514. return true;
  47515. }
  47516. /**
  47517. * Each time we have discontinuity in the playlist, attempt to calculate the location
  47518. * in display of the start of the discontinuity and save that. We also save an accuracy
  47519. * value so that we save values with the most accuracy (closest to 0.)
  47520. *
  47521. * @private
  47522. * @param {SegmentInfo} segmentInfo - The current active request information
  47523. */
  47524. }, {
  47525. key: 'saveDiscontinuitySyncInfo_',
  47526. value: function saveDiscontinuitySyncInfo_(segmentInfo) {
  47527. var playlist = segmentInfo.playlist;
  47528. var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
  47529. // the start of the range and it's accuracy is 0 (greater accuracy values
  47530. // mean more approximation)
  47531. if (segment.discontinuity) {
  47532. this.discontinuities[segment.timeline] = {
  47533. time: segment.start,
  47534. accuracy: 0
  47535. };
  47536. } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  47537. // Search for future discontinuities that we can provide better timing
  47538. // information for and save that information for sync purposes
  47539. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  47540. var segmentIndex = playlist.discontinuityStarts[i];
  47541. var discontinuity = playlist.discontinuitySequence + i + 1;
  47542. var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
  47543. var accuracy = Math.abs(mediaIndexDiff);
  47544. if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
  47545. var time = void 0;
  47546. if (mediaIndexDiff < 0) {
  47547. time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
  47548. } else {
  47549. time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
  47550. }
  47551. this.discontinuities[discontinuity] = {
  47552. time: time,
  47553. accuracy: accuracy
  47554. };
  47555. }
  47556. }
  47557. }
  47558. }
  47559. }]);
  47560. return SyncController;
  47561. }(videojs$1.EventTarget);
  47562. var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
  47563. var self = this;
  47564. var decrypterWorker = function () {
  47565. /*
  47566. * pkcs7.pad
  47567. * https://github.com/brightcove/pkcs7
  47568. *
  47569. * Copyright (c) 2014 Brightcove
  47570. * Licensed under the apache2 license.
  47571. */
  47572. /**
  47573. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  47574. * @param padded {Uint8Array} unencrypted bytes that have been padded
  47575. * @return {Uint8Array} the unpadded bytes
  47576. * @see http://tools.ietf.org/html/rfc5652
  47577. */
  47578. function unpad(padded) {
  47579. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  47580. }
  47581. var classCallCheck = function classCallCheck(instance, Constructor) {
  47582. if (!(instance instanceof Constructor)) {
  47583. throw new TypeError("Cannot call a class as a function");
  47584. }
  47585. };
  47586. var createClass = function () {
  47587. function defineProperties(target, props) {
  47588. for (var i = 0; i < props.length; i++) {
  47589. var descriptor = props[i];
  47590. descriptor.enumerable = descriptor.enumerable || false;
  47591. descriptor.configurable = true;
  47592. if ("value" in descriptor) descriptor.writable = true;
  47593. Object.defineProperty(target, descriptor.key, descriptor);
  47594. }
  47595. }
  47596. return function (Constructor, protoProps, staticProps) {
  47597. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  47598. if (staticProps) defineProperties(Constructor, staticProps);
  47599. return Constructor;
  47600. };
  47601. }();
  47602. var inherits = function inherits(subClass, superClass) {
  47603. if (typeof superClass !== "function" && superClass !== null) {
  47604. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  47605. }
  47606. subClass.prototype = Object.create(superClass && superClass.prototype, {
  47607. constructor: {
  47608. value: subClass,
  47609. enumerable: false,
  47610. writable: true,
  47611. configurable: true
  47612. }
  47613. });
  47614. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  47615. };
  47616. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  47617. if (!self) {
  47618. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  47619. }
  47620. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  47621. };
  47622. /**
  47623. * @file aes.js
  47624. *
  47625. * This file contains an adaptation of the AES decryption algorithm
  47626. * from the Standford Javascript Cryptography Library. That work is
  47627. * covered by the following copyright and permissions notice:
  47628. *
  47629. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  47630. * All rights reserved.
  47631. *
  47632. * Redistribution and use in source and binary forms, with or without
  47633. * modification, are permitted provided that the following conditions are
  47634. * met:
  47635. *
  47636. * 1. Redistributions of source code must retain the above copyright
  47637. * notice, this list of conditions and the following disclaimer.
  47638. *
  47639. * 2. Redistributions in binary form must reproduce the above
  47640. * copyright notice, this list of conditions and the following
  47641. * disclaimer in the documentation and/or other materials provided
  47642. * with the distribution.
  47643. *
  47644. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  47645. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  47646. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  47647. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  47648. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  47649. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  47650. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  47651. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  47652. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  47653. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  47654. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  47655. *
  47656. * The views and conclusions contained in the software and documentation
  47657. * are those of the authors and should not be interpreted as representing
  47658. * official policies, either expressed or implied, of the authors.
  47659. */
  47660. /**
  47661. * Expand the S-box tables.
  47662. *
  47663. * @private
  47664. */
  47665. var precompute = function precompute() {
  47666. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  47667. var encTable = tables[0];
  47668. var decTable = tables[1];
  47669. var sbox = encTable[4];
  47670. var sboxInv = decTable[4];
  47671. var i = void 0;
  47672. var x = void 0;
  47673. var xInv = void 0;
  47674. var d = [];
  47675. var th = [];
  47676. var x2 = void 0;
  47677. var x4 = void 0;
  47678. var x8 = void 0;
  47679. var s = void 0;
  47680. var tEnc = void 0;
  47681. var tDec = void 0; // Compute double and third tables
  47682. for (i = 0; i < 256; i++) {
  47683. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  47684. }
  47685. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  47686. // Compute sbox
  47687. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  47688. s = s >> 8 ^ s & 255 ^ 99;
  47689. sbox[x] = s;
  47690. sboxInv[s] = x; // Compute MixColumns
  47691. x8 = d[x4 = d[x2 = d[x]]];
  47692. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  47693. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  47694. for (i = 0; i < 4; i++) {
  47695. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  47696. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  47697. }
  47698. } // Compactify. Considerable speedup on Firefox.
  47699. for (i = 0; i < 5; i++) {
  47700. encTable[i] = encTable[i].slice(0);
  47701. decTable[i] = decTable[i].slice(0);
  47702. }
  47703. return tables;
  47704. };
  47705. var aesTables = null;
  47706. /**
  47707. * Schedule out an AES key for both encryption and decryption. This
  47708. * is a low-level class. Use a cipher mode to do bulk encryption.
  47709. *
  47710. * @class AES
  47711. * @param key {Array} The key as an array of 4, 6 or 8 words.
  47712. */
  47713. var AES = function () {
  47714. function AES(key) {
  47715. classCallCheck(this, AES);
  47716. /**
  47717. * The expanded S-box and inverse S-box tables. These will be computed
  47718. * on the client so that we don't have to send them down the wire.
  47719. *
  47720. * There are two tables, _tables[0] is for encryption and
  47721. * _tables[1] is for decryption.
  47722. *
  47723. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  47724. * last (_tables[01][4]) is the S-box itself.
  47725. *
  47726. * @private
  47727. */
  47728. // if we have yet to precompute the S-box tables
  47729. // do so now
  47730. if (!aesTables) {
  47731. aesTables = precompute();
  47732. } // then make a copy of that object for use
  47733. 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()]];
  47734. var i = void 0;
  47735. var j = void 0;
  47736. var tmp = void 0;
  47737. var encKey = void 0;
  47738. var decKey = void 0;
  47739. var sbox = this._tables[0][4];
  47740. var decTable = this._tables[1];
  47741. var keyLen = key.length;
  47742. var rcon = 1;
  47743. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  47744. throw new Error('Invalid aes key size');
  47745. }
  47746. encKey = key.slice(0);
  47747. decKey = [];
  47748. this._key = [encKey, decKey]; // schedule encryption keys
  47749. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  47750. tmp = encKey[i - 1]; // apply sbox
  47751. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  47752. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  47753. if (i % keyLen === 0) {
  47754. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  47755. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  47756. }
  47757. }
  47758. encKey[i] = encKey[i - keyLen] ^ tmp;
  47759. } // schedule decryption keys
  47760. for (j = 0; i; j++, i--) {
  47761. tmp = encKey[j & 3 ? i : i - 4];
  47762. if (i <= 4 || j < 4) {
  47763. decKey[j] = tmp;
  47764. } else {
  47765. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  47766. }
  47767. }
  47768. }
  47769. /**
  47770. * Decrypt 16 bytes, specified as four 32-bit words.
  47771. *
  47772. * @param {Number} encrypted0 the first word to decrypt
  47773. * @param {Number} encrypted1 the second word to decrypt
  47774. * @param {Number} encrypted2 the third word to decrypt
  47775. * @param {Number} encrypted3 the fourth word to decrypt
  47776. * @param {Int32Array} out the array to write the decrypted words
  47777. * into
  47778. * @param {Number} offset the offset into the output array to start
  47779. * writing results
  47780. * @return {Array} The plaintext.
  47781. */
  47782. AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  47783. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  47784. var a = encrypted0 ^ key[0];
  47785. var b = encrypted3 ^ key[1];
  47786. var c = encrypted2 ^ key[2];
  47787. var d = encrypted1 ^ key[3];
  47788. var a2 = void 0;
  47789. var b2 = void 0;
  47790. var c2 = void 0; // key.length === 2 ?
  47791. var nInnerRounds = key.length / 4 - 2;
  47792. var i = void 0;
  47793. var kIndex = 4;
  47794. var table = this._tables[1]; // load up the tables
  47795. var table0 = table[0];
  47796. var table1 = table[1];
  47797. var table2 = table[2];
  47798. var table3 = table[3];
  47799. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  47800. for (i = 0; i < nInnerRounds; i++) {
  47801. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  47802. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  47803. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  47804. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  47805. kIndex += 4;
  47806. a = a2;
  47807. b = b2;
  47808. c = c2;
  47809. } // Last round.
  47810. for (i = 0; i < 4; i++) {
  47811. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  47812. a2 = a;
  47813. a = b;
  47814. b = c;
  47815. c = d;
  47816. d = a2;
  47817. }
  47818. };
  47819. return AES;
  47820. }();
  47821. /**
  47822. * @file stream.js
  47823. */
  47824. /**
  47825. * A lightweight readable stream implemention that handles event dispatching.
  47826. *
  47827. * @class Stream
  47828. */
  47829. var Stream = function () {
  47830. function Stream() {
  47831. classCallCheck(this, Stream);
  47832. this.listeners = {};
  47833. }
  47834. /**
  47835. * Add a listener for a specified event type.
  47836. *
  47837. * @param {String} type the event name
  47838. * @param {Function} listener the callback to be invoked when an event of
  47839. * the specified type occurs
  47840. */
  47841. Stream.prototype.on = function on(type, listener) {
  47842. if (!this.listeners[type]) {
  47843. this.listeners[type] = [];
  47844. }
  47845. this.listeners[type].push(listener);
  47846. };
  47847. /**
  47848. * Remove a listener for a specified event type.
  47849. *
  47850. * @param {String} type the event name
  47851. * @param {Function} listener a function previously registered for this
  47852. * type of event through `on`
  47853. * @return {Boolean} if we could turn it off or not
  47854. */
  47855. Stream.prototype.off = function off(type, listener) {
  47856. if (!this.listeners[type]) {
  47857. return false;
  47858. }
  47859. var index = this.listeners[type].indexOf(listener);
  47860. this.listeners[type].splice(index, 1);
  47861. return index > -1;
  47862. };
  47863. /**
  47864. * Trigger an event of the specified type on this stream. Any additional
  47865. * arguments to this function are passed as parameters to event listeners.
  47866. *
  47867. * @param {String} type the event name
  47868. */
  47869. Stream.prototype.trigger = function trigger(type) {
  47870. var callbacks = this.listeners[type];
  47871. if (!callbacks) {
  47872. return;
  47873. } // Slicing the arguments on every invocation of this method
  47874. // can add a significant amount of overhead. Avoid the
  47875. // intermediate object creation for the common case of a
  47876. // single callback argument
  47877. if (arguments.length === 2) {
  47878. var length = callbacks.length;
  47879. for (var i = 0; i < length; ++i) {
  47880. callbacks[i].call(this, arguments[1]);
  47881. }
  47882. } else {
  47883. var args = Array.prototype.slice.call(arguments, 1);
  47884. var _length = callbacks.length;
  47885. for (var _i = 0; _i < _length; ++_i) {
  47886. callbacks[_i].apply(this, args);
  47887. }
  47888. }
  47889. };
  47890. /**
  47891. * Destroys the stream and cleans up.
  47892. */
  47893. Stream.prototype.dispose = function dispose() {
  47894. this.listeners = {};
  47895. };
  47896. /**
  47897. * Forwards all `data` events on this stream to the destination stream. The
  47898. * destination stream should provide a method `push` to receive the data
  47899. * events as they arrive.
  47900. *
  47901. * @param {Stream} destination the stream that will receive all `data` events
  47902. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  47903. */
  47904. Stream.prototype.pipe = function pipe(destination) {
  47905. this.on('data', function (data) {
  47906. destination.push(data);
  47907. });
  47908. };
  47909. return Stream;
  47910. }();
  47911. /**
  47912. * @file async-stream.js
  47913. */
  47914. /**
  47915. * A wrapper around the Stream class to use setTiemout
  47916. * and run stream "jobs" Asynchronously
  47917. *
  47918. * @class AsyncStream
  47919. * @extends Stream
  47920. */
  47921. var AsyncStream$$1 = function (_Stream) {
  47922. inherits(AsyncStream$$1, _Stream);
  47923. function AsyncStream$$1() {
  47924. classCallCheck(this, AsyncStream$$1);
  47925. var _this = possibleConstructorReturn(this, _Stream.call(this, Stream));
  47926. _this.jobs = [];
  47927. _this.delay = 1;
  47928. _this.timeout_ = null;
  47929. return _this;
  47930. }
  47931. /**
  47932. * process an async job
  47933. *
  47934. * @private
  47935. */
  47936. AsyncStream$$1.prototype.processJob_ = function processJob_() {
  47937. this.jobs.shift()();
  47938. if (this.jobs.length) {
  47939. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  47940. } else {
  47941. this.timeout_ = null;
  47942. }
  47943. };
  47944. /**
  47945. * push a job into the stream
  47946. *
  47947. * @param {Function} job the job to push into the stream
  47948. */
  47949. AsyncStream$$1.prototype.push = function push(job) {
  47950. this.jobs.push(job);
  47951. if (!this.timeout_) {
  47952. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  47953. }
  47954. };
  47955. return AsyncStream$$1;
  47956. }(Stream);
  47957. /**
  47958. * @file decrypter.js
  47959. *
  47960. * An asynchronous implementation of AES-128 CBC decryption with
  47961. * PKCS#7 padding.
  47962. */
  47963. /**
  47964. * Convert network-order (big-endian) bytes into their little-endian
  47965. * representation.
  47966. */
  47967. var ntoh = function ntoh(word) {
  47968. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  47969. };
  47970. /**
  47971. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  47972. *
  47973. * @param {Uint8Array} encrypted the encrypted bytes
  47974. * @param {Uint32Array} key the bytes of the decryption key
  47975. * @param {Uint32Array} initVector the initialization vector (IV) to
  47976. * use for the first round of CBC.
  47977. * @return {Uint8Array} the decrypted bytes
  47978. *
  47979. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  47980. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  47981. * @see https://tools.ietf.org/html/rfc2315
  47982. */
  47983. var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
  47984. // word-level access to the encrypted bytes
  47985. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  47986. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  47987. var decrypted = new Uint8Array(encrypted.byteLength);
  47988. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  47989. // decrypted data
  47990. var init0 = void 0;
  47991. var init1 = void 0;
  47992. var init2 = void 0;
  47993. var init3 = void 0;
  47994. var encrypted0 = void 0;
  47995. var encrypted1 = void 0;
  47996. var encrypted2 = void 0;
  47997. var encrypted3 = void 0; // iteration variable
  47998. var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
  47999. // passed-in reference and easier access
  48000. init0 = initVector[0];
  48001. init1 = initVector[1];
  48002. init2 = initVector[2];
  48003. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  48004. // to each decrypted block
  48005. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  48006. // convert big-endian (network order) words into little-endian
  48007. // (javascript order)
  48008. encrypted0 = ntoh(encrypted32[wordIx]);
  48009. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  48010. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  48011. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  48012. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  48013. // plaintext
  48014. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  48015. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  48016. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  48017. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  48018. init0 = encrypted0;
  48019. init1 = encrypted1;
  48020. init2 = encrypted2;
  48021. init3 = encrypted3;
  48022. }
  48023. return decrypted;
  48024. };
  48025. /**
  48026. * The `Decrypter` class that manages decryption of AES
  48027. * data through `AsyncStream` objects and the `decrypt`
  48028. * function
  48029. *
  48030. * @param {Uint8Array} encrypted the encrypted bytes
  48031. * @param {Uint32Array} key the bytes of the decryption key
  48032. * @param {Uint32Array} initVector the initialization vector (IV) to
  48033. * @param {Function} done the function to run when done
  48034. * @class Decrypter
  48035. */
  48036. var Decrypter$$1 = function () {
  48037. function Decrypter$$1(encrypted, key, initVector, done) {
  48038. classCallCheck(this, Decrypter$$1);
  48039. var step = Decrypter$$1.STEP;
  48040. var encrypted32 = new Int32Array(encrypted.buffer);
  48041. var decrypted = new Uint8Array(encrypted.byteLength);
  48042. var i = 0;
  48043. this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously
  48044. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  48045. for (i = step; i < encrypted32.length; i += step) {
  48046. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  48047. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  48048. } // invoke the done() callback when everything is finished
  48049. this.asyncStream_.push(function () {
  48050. // remove pkcs#7 padding from the decrypted bytes
  48051. done(null, unpad(decrypted));
  48052. });
  48053. }
  48054. /**
  48055. * a getter for step the maximum number of bytes to process at one time
  48056. *
  48057. * @return {Number} the value of step 32000
  48058. */
  48059. /**
  48060. * @private
  48061. */
  48062. Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  48063. return function () {
  48064. var bytes = decrypt$$1(encrypted, key, initVector);
  48065. decrypted.set(bytes, encrypted.byteOffset);
  48066. };
  48067. };
  48068. createClass(Decrypter$$1, null, [{
  48069. key: 'STEP',
  48070. get: function get$$1() {
  48071. // 4 * 8000;
  48072. return 32000;
  48073. }
  48074. }]);
  48075. return Decrypter$$1;
  48076. }();
  48077. /**
  48078. * @file bin-utils.js
  48079. */
  48080. /**
  48081. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  48082. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  48083. *
  48084. * @param {Object} message
  48085. * Object of properties and values to send to the web worker
  48086. * @return {Object}
  48087. * Modified message with TypedArray values expanded
  48088. * @function createTransferableMessage
  48089. */
  48090. var createTransferableMessage = function createTransferableMessage(message) {
  48091. var transferable = {};
  48092. Object.keys(message).forEach(function (key) {
  48093. var value = message[key];
  48094. if (ArrayBuffer.isView(value)) {
  48095. transferable[key] = {
  48096. bytes: value.buffer,
  48097. byteOffset: value.byteOffset,
  48098. byteLength: value.byteLength
  48099. };
  48100. } else {
  48101. transferable[key] = value;
  48102. }
  48103. });
  48104. return transferable;
  48105. };
  48106. /**
  48107. * Our web worker interface so that things can talk to aes-decrypter
  48108. * that will be running in a web worker. the scope is passed to this by
  48109. * webworkify.
  48110. *
  48111. * @param {Object} self
  48112. * the scope for the web worker
  48113. */
  48114. var DecrypterWorker = function DecrypterWorker(self) {
  48115. self.onmessage = function (event) {
  48116. var data = event.data;
  48117. var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
  48118. var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
  48119. var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
  48120. /* eslint-disable no-new, handle-callback-err */
  48121. new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
  48122. self.postMessage(createTransferableMessage({
  48123. source: data.source,
  48124. decrypted: bytes
  48125. }), [bytes.buffer]);
  48126. });
  48127. /* eslint-enable */
  48128. };
  48129. };
  48130. var decrypterWorker = new DecrypterWorker(self);
  48131. return decrypterWorker;
  48132. }();
  48133. });
  48134. /**
  48135. * Convert the properties of an HLS track into an audioTrackKind.
  48136. *
  48137. * @private
  48138. */
  48139. var audioTrackKind_ = function audioTrackKind_(properties) {
  48140. var kind = properties["default"] ? 'main' : 'alternative';
  48141. if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
  48142. kind = 'main-desc';
  48143. }
  48144. return kind;
  48145. };
  48146. /**
  48147. * Pause provided segment loader and playlist loader if active
  48148. *
  48149. * @param {SegmentLoader} segmentLoader
  48150. * SegmentLoader to pause
  48151. * @param {Object} mediaType
  48152. * Active media type
  48153. * @function stopLoaders
  48154. */
  48155. var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
  48156. segmentLoader.abort();
  48157. segmentLoader.pause();
  48158. if (mediaType && mediaType.activePlaylistLoader) {
  48159. mediaType.activePlaylistLoader.pause();
  48160. mediaType.activePlaylistLoader = null;
  48161. }
  48162. };
  48163. /**
  48164. * Start loading provided segment loader and playlist loader
  48165. *
  48166. * @param {PlaylistLoader} playlistLoader
  48167. * PlaylistLoader to start loading
  48168. * @param {Object} mediaType
  48169. * Active media type
  48170. * @function startLoaders
  48171. */
  48172. var startLoaders = function startLoaders(playlistLoader, mediaType) {
  48173. // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
  48174. // playlist loader
  48175. mediaType.activePlaylistLoader = playlistLoader;
  48176. playlistLoader.load();
  48177. };
  48178. /**
  48179. * Returns a function to be called when the media group changes. It performs a
  48180. * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
  48181. * change of group is merely a rendition switch of the same content at another encoding,
  48182. * rather than a change of content, such as switching audio from English to Spanish.
  48183. *
  48184. * @param {String} type
  48185. * MediaGroup type
  48186. * @param {Object} settings
  48187. * Object containing required information for media groups
  48188. * @return {Function}
  48189. * Handler for a non-destructive resync of SegmentLoader when the active media
  48190. * group changes.
  48191. * @function onGroupChanged
  48192. */
  48193. var onGroupChanged = function onGroupChanged(type, settings) {
  48194. return function () {
  48195. var _settings$segmentLoad = settings.segmentLoaders,
  48196. segmentLoader = _settings$segmentLoad[type],
  48197. mainSegmentLoader = _settings$segmentLoad.main,
  48198. mediaType = settings.mediaTypes[type];
  48199. var activeTrack = mediaType.activeTrack();
  48200. var activeGroup = mediaType.activeGroup(activeTrack);
  48201. var previousActiveLoader = mediaType.activePlaylistLoader;
  48202. stopLoaders(segmentLoader, mediaType);
  48203. if (!activeGroup) {
  48204. // there is no group active
  48205. return;
  48206. }
  48207. if (!activeGroup.playlistLoader) {
  48208. if (previousActiveLoader) {
  48209. // The previous group had a playlist loader but the new active group does not
  48210. // this means we are switching from demuxed to muxed audio. In this case we want to
  48211. // do a destructive reset of the main segment loader and not restart the audio
  48212. // loaders.
  48213. mainSegmentLoader.resetEverything();
  48214. }
  48215. return;
  48216. } // Non-destructive resync
  48217. segmentLoader.resyncLoader();
  48218. startLoaders(activeGroup.playlistLoader, mediaType);
  48219. };
  48220. };
  48221. /**
  48222. * Returns a function to be called when the media track changes. It performs a
  48223. * destructive reset of the SegmentLoader to ensure we start loading as close to
  48224. * currentTime as possible.
  48225. *
  48226. * @param {String} type
  48227. * MediaGroup type
  48228. * @param {Object} settings
  48229. * Object containing required information for media groups
  48230. * @return {Function}
  48231. * Handler for a destructive reset of SegmentLoader when the active media
  48232. * track changes.
  48233. * @function onTrackChanged
  48234. */
  48235. var onTrackChanged = function onTrackChanged(type, settings) {
  48236. return function () {
  48237. var _settings$segmentLoad2 = settings.segmentLoaders,
  48238. segmentLoader = _settings$segmentLoad2[type],
  48239. mainSegmentLoader = _settings$segmentLoad2.main,
  48240. mediaType = settings.mediaTypes[type];
  48241. var activeTrack = mediaType.activeTrack();
  48242. var activeGroup = mediaType.activeGroup(activeTrack);
  48243. var previousActiveLoader = mediaType.activePlaylistLoader;
  48244. stopLoaders(segmentLoader, mediaType);
  48245. if (!activeGroup) {
  48246. // there is no group active so we do not want to restart loaders
  48247. return;
  48248. }
  48249. if (!activeGroup.playlistLoader) {
  48250. // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
  48251. // loader for the audio group), we want to do a destructive reset of the main segment
  48252. // loader and not restart the audio loaders
  48253. mainSegmentLoader.resetEverything();
  48254. return;
  48255. }
  48256. if (previousActiveLoader === activeGroup.playlistLoader) {
  48257. // Nothing has actually changed. This can happen because track change events can fire
  48258. // multiple times for a "single" change. One for enabling the new active track, and
  48259. // one for disabling the track that was active
  48260. startLoaders(activeGroup.playlistLoader, mediaType);
  48261. return;
  48262. }
  48263. if (segmentLoader.track) {
  48264. // For WebVTT, set the new text track in the segmentloader
  48265. segmentLoader.track(activeTrack);
  48266. } // destructive reset
  48267. segmentLoader.resetEverything();
  48268. startLoaders(activeGroup.playlistLoader, mediaType);
  48269. };
  48270. };
  48271. var onError = {
  48272. /**
  48273. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  48274. * an error.
  48275. *
  48276. * @param {String} type
  48277. * MediaGroup type
  48278. * @param {Object} settings
  48279. * Object containing required information for media groups
  48280. * @return {Function}
  48281. * Error handler. Logs warning (or error if the playlist is blacklisted) to
  48282. * console and switches back to default audio track.
  48283. * @function onError.AUDIO
  48284. */
  48285. AUDIO: function AUDIO(type, settings) {
  48286. return function () {
  48287. var segmentLoader = settings.segmentLoaders[type],
  48288. mediaType = settings.mediaTypes[type],
  48289. blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
  48290. stopLoaders(segmentLoader, mediaType); // switch back to default audio track
  48291. var activeTrack = mediaType.activeTrack();
  48292. var activeGroup = mediaType.activeGroup();
  48293. var id = (activeGroup.filter(function (group) {
  48294. return group["default"];
  48295. })[0] || activeGroup[0]).id;
  48296. var defaultTrack = mediaType.tracks[id];
  48297. if (activeTrack === defaultTrack) {
  48298. // Default track encountered an error. All we can do now is blacklist the current
  48299. // rendition and hope another will switch audio groups
  48300. blacklistCurrentPlaylist({
  48301. message: 'Problem encountered loading the default audio track.'
  48302. });
  48303. return;
  48304. }
  48305. videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
  48306. for (var trackId in mediaType.tracks) {
  48307. mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
  48308. }
  48309. mediaType.onTrackChanged();
  48310. };
  48311. },
  48312. /**
  48313. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  48314. * an error.
  48315. *
  48316. * @param {String} type
  48317. * MediaGroup type
  48318. * @param {Object} settings
  48319. * Object containing required information for media groups
  48320. * @return {Function}
  48321. * Error handler. Logs warning to console and disables the active subtitle track
  48322. * @function onError.SUBTITLES
  48323. */
  48324. SUBTITLES: function SUBTITLES(type, settings) {
  48325. return function () {
  48326. var segmentLoader = settings.segmentLoaders[type],
  48327. mediaType = settings.mediaTypes[type];
  48328. videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
  48329. stopLoaders(segmentLoader, mediaType);
  48330. var track = mediaType.activeTrack();
  48331. if (track) {
  48332. track.mode = 'disabled';
  48333. }
  48334. mediaType.onTrackChanged();
  48335. };
  48336. }
  48337. };
  48338. var setupListeners = {
  48339. /**
  48340. * Setup event listeners for audio playlist loader
  48341. *
  48342. * @param {String} type
  48343. * MediaGroup type
  48344. * @param {PlaylistLoader|null} playlistLoader
  48345. * PlaylistLoader to register listeners on
  48346. * @param {Object} settings
  48347. * Object containing required information for media groups
  48348. * @function setupListeners.AUDIO
  48349. */
  48350. AUDIO: function AUDIO(type, playlistLoader, settings) {
  48351. if (!playlistLoader) {
  48352. // no playlist loader means audio will be muxed with the video
  48353. return;
  48354. }
  48355. var tech = settings.tech,
  48356. requestOptions = settings.requestOptions,
  48357. segmentLoader = settings.segmentLoaders[type];
  48358. playlistLoader.on('loadedmetadata', function () {
  48359. var media = playlistLoader.media();
  48360. segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
  48361. // permits, start downloading segments
  48362. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  48363. segmentLoader.load();
  48364. }
  48365. });
  48366. playlistLoader.on('loadedplaylist', function () {
  48367. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  48368. if (!tech.paused()) {
  48369. segmentLoader.load();
  48370. }
  48371. });
  48372. playlistLoader.on('error', onError[type](type, settings));
  48373. },
  48374. /**
  48375. * Setup event listeners for subtitle playlist loader
  48376. *
  48377. * @param {String} type
  48378. * MediaGroup type
  48379. * @param {PlaylistLoader|null} playlistLoader
  48380. * PlaylistLoader to register listeners on
  48381. * @param {Object} settings
  48382. * Object containing required information for media groups
  48383. * @function setupListeners.SUBTITLES
  48384. */
  48385. SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
  48386. var tech = settings.tech,
  48387. requestOptions = settings.requestOptions,
  48388. segmentLoader = settings.segmentLoaders[type],
  48389. mediaType = settings.mediaTypes[type];
  48390. playlistLoader.on('loadedmetadata', function () {
  48391. var media = playlistLoader.media();
  48392. segmentLoader.playlist(media, requestOptions);
  48393. segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
  48394. // permits, start downloading segments
  48395. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  48396. segmentLoader.load();
  48397. }
  48398. });
  48399. playlistLoader.on('loadedplaylist', function () {
  48400. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  48401. if (!tech.paused()) {
  48402. segmentLoader.load();
  48403. }
  48404. });
  48405. playlistLoader.on('error', onError[type](type, settings));
  48406. }
  48407. };
  48408. var initialize = {
  48409. /**
  48410. * Setup PlaylistLoaders and AudioTracks for the audio groups
  48411. *
  48412. * @param {String} type
  48413. * MediaGroup type
  48414. * @param {Object} settings
  48415. * Object containing required information for media groups
  48416. * @function initialize.AUDIO
  48417. */
  48418. 'AUDIO': function AUDIO(type, settings) {
  48419. var hls = settings.hls,
  48420. sourceType = settings.sourceType,
  48421. segmentLoader = settings.segmentLoaders[type],
  48422. requestOptions = settings.requestOptions,
  48423. mediaGroups = settings.master.mediaGroups,
  48424. _settings$mediaTypes$ = settings.mediaTypes[type],
  48425. groups = _settings$mediaTypes$.groups,
  48426. tracks = _settings$mediaTypes$.tracks,
  48427. masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none
  48428. if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
  48429. mediaGroups[type] = {
  48430. main: {
  48431. "default": {
  48432. "default": true
  48433. }
  48434. }
  48435. };
  48436. }
  48437. for (var groupId in mediaGroups[type]) {
  48438. if (!groups[groupId]) {
  48439. groups[groupId] = [];
  48440. } // List of playlists that have an AUDIO attribute value matching the current
  48441. // group ID
  48442. for (var variantLabel in mediaGroups[type][groupId]) {
  48443. var properties = mediaGroups[type][groupId][variantLabel];
  48444. var playlistLoader = void 0;
  48445. if (properties.resolvedUri) {
  48446. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  48447. } else if (properties.playlists && sourceType === 'dash') {
  48448. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  48449. } else {
  48450. // no resolvedUri means the audio is muxed with the video when using this
  48451. // audio track
  48452. playlistLoader = null;
  48453. }
  48454. properties = videojs$1.mergeOptions({
  48455. id: variantLabel,
  48456. playlistLoader: playlistLoader
  48457. }, properties);
  48458. setupListeners[type](type, properties.playlistLoader, settings);
  48459. groups[groupId].push(properties);
  48460. if (typeof tracks[variantLabel] === 'undefined') {
  48461. var track = new videojs$1.AudioTrack({
  48462. id: variantLabel,
  48463. kind: audioTrackKind_(properties),
  48464. enabled: false,
  48465. language: properties.language,
  48466. "default": properties["default"],
  48467. label: variantLabel
  48468. });
  48469. tracks[variantLabel] = track;
  48470. }
  48471. }
  48472. } // setup single error event handler for the segment loader
  48473. segmentLoader.on('error', onError[type](type, settings));
  48474. },
  48475. /**
  48476. * Setup PlaylistLoaders and TextTracks for the subtitle groups
  48477. *
  48478. * @param {String} type
  48479. * MediaGroup type
  48480. * @param {Object} settings
  48481. * Object containing required information for media groups
  48482. * @function initialize.SUBTITLES
  48483. */
  48484. 'SUBTITLES': function SUBTITLES(type, settings) {
  48485. var tech = settings.tech,
  48486. hls = settings.hls,
  48487. sourceType = settings.sourceType,
  48488. segmentLoader = settings.segmentLoaders[type],
  48489. requestOptions = settings.requestOptions,
  48490. mediaGroups = settings.master.mediaGroups,
  48491. _settings$mediaTypes$2 = settings.mediaTypes[type],
  48492. groups = _settings$mediaTypes$2.groups,
  48493. tracks = _settings$mediaTypes$2.tracks,
  48494. masterPlaylistLoader = settings.masterPlaylistLoader;
  48495. for (var groupId in mediaGroups[type]) {
  48496. if (!groups[groupId]) {
  48497. groups[groupId] = [];
  48498. }
  48499. for (var variantLabel in mediaGroups[type][groupId]) {
  48500. if (mediaGroups[type][groupId][variantLabel].forced) {
  48501. // Subtitle playlists with the forced attribute are not selectable in Safari.
  48502. // According to Apple's HLS Authoring Specification:
  48503. // If content has forced subtitles and regular subtitles in a given language,
  48504. // the regular subtitles track in that language MUST contain both the forced
  48505. // subtitles and the regular subtitles for that language.
  48506. // Because of this requirement and that Safari does not add forced subtitles,
  48507. // forced subtitles are skipped here to maintain consistent experience across
  48508. // all platforms
  48509. continue;
  48510. }
  48511. var properties = mediaGroups[type][groupId][variantLabel];
  48512. var playlistLoader = void 0;
  48513. if (sourceType === 'hls') {
  48514. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  48515. } else if (sourceType === 'dash') {
  48516. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  48517. }
  48518. properties = videojs$1.mergeOptions({
  48519. id: variantLabel,
  48520. playlistLoader: playlistLoader
  48521. }, properties);
  48522. setupListeners[type](type, properties.playlistLoader, settings);
  48523. groups[groupId].push(properties);
  48524. if (typeof tracks[variantLabel] === 'undefined') {
  48525. var track = tech.addRemoteTextTrack({
  48526. id: variantLabel,
  48527. kind: 'subtitles',
  48528. "default": properties["default"] && properties.autoselect,
  48529. language: properties.language,
  48530. label: variantLabel
  48531. }, false).track;
  48532. tracks[variantLabel] = track;
  48533. }
  48534. }
  48535. } // setup single error event handler for the segment loader
  48536. segmentLoader.on('error', onError[type](type, settings));
  48537. },
  48538. /**
  48539. * Setup TextTracks for the closed-caption groups
  48540. *
  48541. * @param {String} type
  48542. * MediaGroup type
  48543. * @param {Object} settings
  48544. * Object containing required information for media groups
  48545. * @function initialize['CLOSED-CAPTIONS']
  48546. */
  48547. 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
  48548. var tech = settings.tech,
  48549. mediaGroups = settings.master.mediaGroups,
  48550. _settings$mediaTypes$3 = settings.mediaTypes[type],
  48551. groups = _settings$mediaTypes$3.groups,
  48552. tracks = _settings$mediaTypes$3.tracks;
  48553. for (var groupId in mediaGroups[type]) {
  48554. if (!groups[groupId]) {
  48555. groups[groupId] = [];
  48556. }
  48557. for (var variantLabel in mediaGroups[type][groupId]) {
  48558. var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that
  48559. // doesn't use a CCx INSTREAM-ID
  48560. if (!properties.instreamId.match(/CC\d/)) {
  48561. continue;
  48562. } // No PlaylistLoader is required for Closed-Captions because the captions are
  48563. // embedded within the video stream
  48564. groups[groupId].push(videojs$1.mergeOptions({
  48565. id: variantLabel
  48566. }, properties));
  48567. if (typeof tracks[variantLabel] === 'undefined') {
  48568. var track = tech.addRemoteTextTrack({
  48569. id: properties.instreamId,
  48570. kind: 'captions',
  48571. "default": properties["default"] && properties.autoselect,
  48572. language: properties.language,
  48573. label: variantLabel
  48574. }, false).track;
  48575. tracks[variantLabel] = track;
  48576. }
  48577. }
  48578. }
  48579. }
  48580. };
  48581. /**
  48582. * Returns a function used to get the active group of the provided type
  48583. *
  48584. * @param {String} type
  48585. * MediaGroup type
  48586. * @param {Object} settings
  48587. * Object containing required information for media groups
  48588. * @return {Function}
  48589. * Function that returns the active media group for the provided type. Takes an
  48590. * optional parameter {TextTrack} track. If no track is provided, a list of all
  48591. * variants in the group, otherwise the variant corresponding to the provided
  48592. * track is returned.
  48593. * @function activeGroup
  48594. */
  48595. var activeGroup = function activeGroup(type, settings) {
  48596. return function (track) {
  48597. var masterPlaylistLoader = settings.masterPlaylistLoader,
  48598. groups = settings.mediaTypes[type].groups;
  48599. var media = masterPlaylistLoader.media();
  48600. if (!media) {
  48601. return null;
  48602. }
  48603. var variants = null;
  48604. if (media.attributes[type]) {
  48605. variants = groups[media.attributes[type]];
  48606. }
  48607. variants = variants || groups.main;
  48608. if (typeof track === 'undefined') {
  48609. return variants;
  48610. }
  48611. if (track === null) {
  48612. // An active track was specified so a corresponding group is expected. track === null
  48613. // means no track is currently active so there is no corresponding group
  48614. return null;
  48615. }
  48616. return variants.filter(function (props) {
  48617. return props.id === track.id;
  48618. })[0] || null;
  48619. };
  48620. };
  48621. var activeTrack = {
  48622. /**
  48623. * Returns a function used to get the active track of type provided
  48624. *
  48625. * @param {String} type
  48626. * MediaGroup type
  48627. * @param {Object} settings
  48628. * Object containing required information for media groups
  48629. * @return {Function}
  48630. * Function that returns the active media track for the provided type. Returns
  48631. * null if no track is active
  48632. * @function activeTrack.AUDIO
  48633. */
  48634. AUDIO: function AUDIO(type, settings) {
  48635. return function () {
  48636. var tracks = settings.mediaTypes[type].tracks;
  48637. for (var id in tracks) {
  48638. if (tracks[id].enabled) {
  48639. return tracks[id];
  48640. }
  48641. }
  48642. return null;
  48643. };
  48644. },
  48645. /**
  48646. * Returns a function used to get the active track of type provided
  48647. *
  48648. * @param {String} type
  48649. * MediaGroup type
  48650. * @param {Object} settings
  48651. * Object containing required information for media groups
  48652. * @return {Function}
  48653. * Function that returns the active media track for the provided type. Returns
  48654. * null if no track is active
  48655. * @function activeTrack.SUBTITLES
  48656. */
  48657. SUBTITLES: function SUBTITLES(type, settings) {
  48658. return function () {
  48659. var tracks = settings.mediaTypes[type].tracks;
  48660. for (var id in tracks) {
  48661. if (tracks[id].mode === 'showing') {
  48662. return tracks[id];
  48663. }
  48664. }
  48665. return null;
  48666. };
  48667. }
  48668. };
  48669. /**
  48670. * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
  48671. * Closed-Captions) specified in the master manifest.
  48672. *
  48673. * @param {Object} settings
  48674. * Object containing required information for setting up the media groups
  48675. * @param {SegmentLoader} settings.segmentLoaders.AUDIO
  48676. * Audio segment loader
  48677. * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
  48678. * Subtitle segment loader
  48679. * @param {SegmentLoader} settings.segmentLoaders.main
  48680. * Main segment loader
  48681. * @param {Tech} settings.tech
  48682. * The tech of the player
  48683. * @param {Object} settings.requestOptions
  48684. * XHR request options used by the segment loaders
  48685. * @param {PlaylistLoader} settings.masterPlaylistLoader
  48686. * PlaylistLoader for the master source
  48687. * @param {HlsHandler} settings.hls
  48688. * HLS SourceHandler
  48689. * @param {Object} settings.master
  48690. * The parsed master manifest
  48691. * @param {Object} settings.mediaTypes
  48692. * Object to store the loaders, tracks, and utility methods for each media type
  48693. * @param {Function} settings.blacklistCurrentPlaylist
  48694. * Blacklists the current rendition and forces a rendition switch.
  48695. * @function setupMediaGroups
  48696. */
  48697. var setupMediaGroups = function setupMediaGroups(settings) {
  48698. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  48699. initialize[type](type, settings);
  48700. });
  48701. var mediaTypes = settings.mediaTypes,
  48702. masterPlaylistLoader = settings.masterPlaylistLoader,
  48703. tech = settings.tech,
  48704. hls = settings.hls; // setup active group and track getters and change event handlers
  48705. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  48706. mediaTypes[type].activeGroup = activeGroup(type, settings);
  48707. mediaTypes[type].activeTrack = activeTrack[type](type, settings);
  48708. mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
  48709. mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
  48710. }); // DO NOT enable the default subtitle or caption track.
  48711. // DO enable the default audio track
  48712. var audioGroup = mediaTypes.AUDIO.activeGroup();
  48713. var groupId = (audioGroup.filter(function (group) {
  48714. return group["default"];
  48715. })[0] || audioGroup[0]).id;
  48716. mediaTypes.AUDIO.tracks[groupId].enabled = true;
  48717. mediaTypes.AUDIO.onTrackChanged();
  48718. masterPlaylistLoader.on('mediachange', function () {
  48719. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  48720. return mediaTypes[type].onGroupChanged();
  48721. });
  48722. }); // custom audio track change event handler for usage event
  48723. var onAudioTrackChanged = function onAudioTrackChanged() {
  48724. mediaTypes.AUDIO.onTrackChanged();
  48725. tech.trigger({
  48726. type: 'usage',
  48727. name: 'hls-audio-change'
  48728. });
  48729. };
  48730. tech.audioTracks().addEventListener('change', onAudioTrackChanged);
  48731. tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  48732. hls.on('dispose', function () {
  48733. tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
  48734. tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  48735. }); // clear existing audio tracks and add the ones we just created
  48736. tech.clearTracks('audio');
  48737. for (var id in mediaTypes.AUDIO.tracks) {
  48738. tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
  48739. }
  48740. };
  48741. /**
  48742. * Creates skeleton object used to store the loaders, tracks, and utility methods for each
  48743. * media type
  48744. *
  48745. * @return {Object}
  48746. * Object to store the loaders, tracks, and utility methods for each media type
  48747. * @function createMediaTypes
  48748. */
  48749. var createMediaTypes = function createMediaTypes() {
  48750. var mediaTypes = {};
  48751. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  48752. mediaTypes[type] = {
  48753. groups: {},
  48754. tracks: {},
  48755. activePlaylistLoader: null,
  48756. activeGroup: noop$1,
  48757. activeTrack: noop$1,
  48758. onGroupChanged: noop$1,
  48759. onTrackChanged: noop$1
  48760. };
  48761. });
  48762. return mediaTypes;
  48763. };
  48764. /**
  48765. * @file master-playlist-controller.js
  48766. */
  48767. var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
  48768. var Hls = void 0; // SegmentLoader stats that need to have each loader's
  48769. // values summed to calculate the final value
  48770. var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
  48771. var sumLoaderStat = function sumLoaderStat(stat) {
  48772. return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
  48773. };
  48774. /**
  48775. * the master playlist controller controller all interactons
  48776. * between playlists and segmentloaders. At this time this mainly
  48777. * involves a master playlist and a series of audio playlists
  48778. * if they are available
  48779. *
  48780. * @class MasterPlaylistController
  48781. * @extends videojs.EventTarget
  48782. */
  48783. var MasterPlaylistController = function (_videojs$EventTarget) {
  48784. inherits$1(MasterPlaylistController, _videojs$EventTarget);
  48785. function MasterPlaylistController(options) {
  48786. classCallCheck$1(this, MasterPlaylistController);
  48787. var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
  48788. var url = options.url,
  48789. handleManifestRedirects = options.handleManifestRedirects,
  48790. withCredentials = options.withCredentials,
  48791. tech = options.tech,
  48792. bandwidth = options.bandwidth,
  48793. externHls = options.externHls,
  48794. useCueTags = options.useCueTags,
  48795. blacklistDuration = options.blacklistDuration,
  48796. enableLowInitialPlaylist = options.enableLowInitialPlaylist,
  48797. sourceType = options.sourceType,
  48798. seekTo = options.seekTo,
  48799. cacheEncryptionKeys = options.cacheEncryptionKeys;
  48800. if (!url) {
  48801. throw new Error('A non-empty playlist URL is required');
  48802. }
  48803. Hls = externHls;
  48804. _this.withCredentials = withCredentials;
  48805. _this.tech_ = tech;
  48806. _this.hls_ = tech.hls;
  48807. _this.seekTo_ = seekTo;
  48808. _this.sourceType_ = sourceType;
  48809. _this.useCueTags_ = useCueTags;
  48810. _this.blacklistDuration = blacklistDuration;
  48811. _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
  48812. if (_this.useCueTags_) {
  48813. _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
  48814. _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
  48815. }
  48816. _this.requestOptions_ = {
  48817. withCredentials: withCredentials,
  48818. handleManifestRedirects: handleManifestRedirects,
  48819. timeout: null
  48820. };
  48821. _this.mediaTypes_ = createMediaTypes();
  48822. _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player
  48823. _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
  48824. _this.seekable_ = videojs$1.createTimeRanges();
  48825. _this.hasPlayed_ = function () {
  48826. return false;
  48827. };
  48828. _this.syncController_ = new SyncController(options);
  48829. _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
  48830. kind: 'metadata',
  48831. label: 'segment-metadata'
  48832. }, false).track;
  48833. _this.decrypter_ = new Decrypter$1();
  48834. _this.inbandTextTracks_ = {};
  48835. var segmentLoaderSettings = {
  48836. hls: _this.hls_,
  48837. mediaSource: _this.mediaSource,
  48838. currentTime: _this.tech_.currentTime.bind(_this.tech_),
  48839. seekable: function seekable$$1() {
  48840. return _this.seekable();
  48841. },
  48842. seeking: function seeking() {
  48843. return _this.tech_.seeking();
  48844. },
  48845. duration: function duration$$1() {
  48846. return _this.mediaSource.duration;
  48847. },
  48848. hasPlayed: function hasPlayed() {
  48849. return _this.hasPlayed_();
  48850. },
  48851. goalBufferLength: function goalBufferLength() {
  48852. return _this.goalBufferLength();
  48853. },
  48854. bandwidth: bandwidth,
  48855. syncController: _this.syncController_,
  48856. decrypter: _this.decrypter_,
  48857. sourceType: _this.sourceType_,
  48858. inbandTextTracks: _this.inbandTextTracks_,
  48859. cacheEncryptionKeys: cacheEncryptionKeys
  48860. };
  48861. _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_);
  48862. _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
  48863. // combined audio/video or just video when alternate audio track is selected
  48864. _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  48865. segmentMetadataTrack: _this.segmentMetadataTrack_,
  48866. loaderType: 'main'
  48867. }), options); // alternate audio track
  48868. _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  48869. loaderType: 'audio'
  48870. }), options);
  48871. _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  48872. loaderType: 'vtt'
  48873. }), options);
  48874. _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters
  48875. loaderStats.forEach(function (stat) {
  48876. _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
  48877. });
  48878. _this.logger_ = logger('MPC');
  48879. _this.masterPlaylistLoader_.load();
  48880. return _this;
  48881. }
  48882. /**
  48883. * Register event handlers on the master playlist loader. A helper
  48884. * function for construction time.
  48885. *
  48886. * @private
  48887. */
  48888. createClass$1(MasterPlaylistController, [{
  48889. key: 'setupMasterPlaylistLoaderListeners_',
  48890. value: function setupMasterPlaylistLoaderListeners_() {
  48891. var _this2 = this;
  48892. this.masterPlaylistLoader_.on('loadedmetadata', function () {
  48893. var media = _this2.masterPlaylistLoader_.media();
  48894. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  48895. // timeout the request.
  48896. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  48897. _this2.requestOptions_.timeout = 0;
  48898. } else {
  48899. _this2.requestOptions_.timeout = requestTimeout;
  48900. } // if this isn't a live video and preload permits, start
  48901. // downloading segments
  48902. if (media.endList && _this2.tech_.preload() !== 'none') {
  48903. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  48904. _this2.mainSegmentLoader_.load();
  48905. }
  48906. setupMediaGroups({
  48907. sourceType: _this2.sourceType_,
  48908. segmentLoaders: {
  48909. AUDIO: _this2.audioSegmentLoader_,
  48910. SUBTITLES: _this2.subtitleSegmentLoader_,
  48911. main: _this2.mainSegmentLoader_
  48912. },
  48913. tech: _this2.tech_,
  48914. requestOptions: _this2.requestOptions_,
  48915. masterPlaylistLoader: _this2.masterPlaylistLoader_,
  48916. hls: _this2.hls_,
  48917. master: _this2.master(),
  48918. mediaTypes: _this2.mediaTypes_,
  48919. blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
  48920. });
  48921. _this2.triggerPresenceUsage_(_this2.master(), media);
  48922. try {
  48923. _this2.setupSourceBuffers_();
  48924. } catch (e) {
  48925. videojs$1.log.warn('Failed to create SourceBuffers', e);
  48926. return _this2.mediaSource.endOfStream('decode');
  48927. }
  48928. _this2.setupFirstPlay();
  48929. if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
  48930. _this2.trigger('selectedinitialmedia');
  48931. } else {
  48932. // We must wait for the active audio playlist loader to
  48933. // finish setting up before triggering this event so the
  48934. // representations API and EME setup is correct
  48935. _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
  48936. _this2.trigger('selectedinitialmedia');
  48937. });
  48938. }
  48939. });
  48940. this.masterPlaylistLoader_.on('loadedplaylist', function () {
  48941. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  48942. if (!updatedPlaylist) {
  48943. // blacklist any variants that are not supported by the browser before selecting
  48944. // an initial media as the playlist selectors do not consider browser support
  48945. _this2.excludeUnsupportedVariants_();
  48946. var selectedMedia = void 0;
  48947. if (_this2.enableLowInitialPlaylist) {
  48948. selectedMedia = _this2.selectInitialPlaylist();
  48949. }
  48950. if (!selectedMedia) {
  48951. selectedMedia = _this2.selectPlaylist();
  48952. }
  48953. _this2.initialMedia_ = selectedMedia;
  48954. _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
  48955. return;
  48956. }
  48957. if (_this2.useCueTags_) {
  48958. _this2.updateAdCues_(updatedPlaylist);
  48959. } // TODO: Create a new event on the PlaylistLoader that signals
  48960. // that the segments have changed in some way and use that to
  48961. // update the SegmentLoader instead of doing it twice here and
  48962. // on `mediachange`
  48963. _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
  48964. _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running,
  48965. // as it is possible that it was temporarily stopped while waiting for
  48966. // a playlist (e.g., in case the playlist errored and we re-requested it).
  48967. if (!_this2.tech_.paused()) {
  48968. _this2.mainSegmentLoader_.load();
  48969. if (_this2.audioSegmentLoader_) {
  48970. _this2.audioSegmentLoader_.load();
  48971. }
  48972. }
  48973. if (!updatedPlaylist.endList) {
  48974. var addSeekableRange = function addSeekableRange() {
  48975. var seekable$$1 = _this2.seekable();
  48976. if (seekable$$1.length !== 0) {
  48977. _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
  48978. }
  48979. };
  48980. if (_this2.duration() !== Infinity) {
  48981. var onDurationchange = function onDurationchange() {
  48982. if (_this2.duration() === Infinity) {
  48983. addSeekableRange();
  48984. } else {
  48985. _this2.tech_.one('durationchange', onDurationchange);
  48986. }
  48987. };
  48988. _this2.tech_.one('durationchange', onDurationchange);
  48989. } else {
  48990. addSeekableRange();
  48991. }
  48992. }
  48993. });
  48994. this.masterPlaylistLoader_.on('error', function () {
  48995. _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
  48996. });
  48997. this.masterPlaylistLoader_.on('mediachanging', function () {
  48998. _this2.mainSegmentLoader_.abort();
  48999. _this2.mainSegmentLoader_.pause();
  49000. });
  49001. this.masterPlaylistLoader_.on('mediachange', function () {
  49002. var media = _this2.masterPlaylistLoader_.media();
  49003. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  49004. // timeout the request.
  49005. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  49006. _this2.requestOptions_.timeout = 0;
  49007. } else {
  49008. _this2.requestOptions_.timeout = requestTimeout;
  49009. } // TODO: Create a new event on the PlaylistLoader that signals
  49010. // that the segments have changed in some way and use that to
  49011. // update the SegmentLoader instead of doing it twice here and
  49012. // on `loadedplaylist`
  49013. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  49014. _this2.mainSegmentLoader_.load();
  49015. _this2.tech_.trigger({
  49016. type: 'mediachange',
  49017. bubbles: true
  49018. });
  49019. });
  49020. this.masterPlaylistLoader_.on('playlistunchanged', function () {
  49021. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  49022. var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
  49023. if (playlistOutdated) {
  49024. // Playlist has stopped updating and we're stuck at its end. Try to
  49025. // blacklist it and switch to another playlist in the hope that that
  49026. // one is updating (and give the player a chance to re-adjust to the
  49027. // safe live point).
  49028. _this2.blacklistCurrentPlaylist({
  49029. message: 'Playlist no longer updating.'
  49030. }); // useful for monitoring QoS
  49031. _this2.tech_.trigger('playliststuck');
  49032. }
  49033. });
  49034. this.masterPlaylistLoader_.on('renditiondisabled', function () {
  49035. _this2.tech_.trigger({
  49036. type: 'usage',
  49037. name: 'hls-rendition-disabled'
  49038. });
  49039. });
  49040. this.masterPlaylistLoader_.on('renditionenabled', function () {
  49041. _this2.tech_.trigger({
  49042. type: 'usage',
  49043. name: 'hls-rendition-enabled'
  49044. });
  49045. });
  49046. }
  49047. /**
  49048. * A helper function for triggerring presence usage events once per source
  49049. *
  49050. * @private
  49051. */
  49052. }, {
  49053. key: 'triggerPresenceUsage_',
  49054. value: function triggerPresenceUsage_(master, media) {
  49055. var mediaGroups = master.mediaGroups || {};
  49056. var defaultDemuxed = true;
  49057. var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
  49058. for (var mediaGroup in mediaGroups.AUDIO) {
  49059. for (var label in mediaGroups.AUDIO[mediaGroup]) {
  49060. var properties = mediaGroups.AUDIO[mediaGroup][label];
  49061. if (!properties.uri) {
  49062. defaultDemuxed = false;
  49063. }
  49064. }
  49065. }
  49066. if (defaultDemuxed) {
  49067. this.tech_.trigger({
  49068. type: 'usage',
  49069. name: 'hls-demuxed'
  49070. });
  49071. }
  49072. if (Object.keys(mediaGroups.SUBTITLES).length) {
  49073. this.tech_.trigger({
  49074. type: 'usage',
  49075. name: 'hls-webvtt'
  49076. });
  49077. }
  49078. if (Hls.Playlist.isAes(media)) {
  49079. this.tech_.trigger({
  49080. type: 'usage',
  49081. name: 'hls-aes'
  49082. });
  49083. }
  49084. if (Hls.Playlist.isFmp4(media)) {
  49085. this.tech_.trigger({
  49086. type: 'usage',
  49087. name: 'hls-fmp4'
  49088. });
  49089. }
  49090. if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
  49091. this.tech_.trigger({
  49092. type: 'usage',
  49093. name: 'hls-alternate-audio'
  49094. });
  49095. }
  49096. if (this.useCueTags_) {
  49097. this.tech_.trigger({
  49098. type: 'usage',
  49099. name: 'hls-playlist-cue-tags'
  49100. });
  49101. }
  49102. }
  49103. /**
  49104. * Register event handlers on the segment loaders. A helper function
  49105. * for construction time.
  49106. *
  49107. * @private
  49108. */
  49109. }, {
  49110. key: 'setupSegmentLoaderListeners_',
  49111. value: function setupSegmentLoaderListeners_() {
  49112. var _this3 = this;
  49113. this.mainSegmentLoader_.on('bandwidthupdate', function () {
  49114. var nextPlaylist = _this3.selectPlaylist();
  49115. var currentPlaylist = _this3.masterPlaylistLoader_.media();
  49116. var buffered = _this3.tech_.buffered();
  49117. var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
  49118. var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account.
  49119. // This is because in LIVE, the player plays 3 segments from the end of the
  49120. // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
  49121. // in those segments, a viewer will never experience a rendition upswitch.
  49122. if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD
  49123. // duration is below the max potential low water line
  49124. _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but
  49125. nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of
  49126. // buffer while loading a higher rendition.
  49127. forwardBuffer >= bufferLowWaterLine) {
  49128. _this3.masterPlaylistLoader_.media(nextPlaylist);
  49129. }
  49130. _this3.tech_.trigger('bandwidthupdate');
  49131. });
  49132. this.mainSegmentLoader_.on('progress', function () {
  49133. _this3.trigger('progress');
  49134. });
  49135. this.mainSegmentLoader_.on('error', function () {
  49136. _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
  49137. });
  49138. this.mainSegmentLoader_.on('syncinfoupdate', function () {
  49139. _this3.onSyncInfoUpdate_();
  49140. });
  49141. this.mainSegmentLoader_.on('timestampoffset', function () {
  49142. _this3.tech_.trigger({
  49143. type: 'usage',
  49144. name: 'hls-timestamp-offset'
  49145. });
  49146. });
  49147. this.audioSegmentLoader_.on('syncinfoupdate', function () {
  49148. _this3.onSyncInfoUpdate_();
  49149. });
  49150. this.mainSegmentLoader_.on('ended', function () {
  49151. _this3.onEndOfStream();
  49152. });
  49153. this.mainSegmentLoader_.on('earlyabort', function () {
  49154. _this3.blacklistCurrentPlaylist({
  49155. message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
  49156. }, ABORT_EARLY_BLACKLIST_SECONDS);
  49157. });
  49158. this.mainSegmentLoader_.on('reseteverything', function () {
  49159. // If playing an MTS stream, a videojs.MediaSource is listening for
  49160. // hls-reset to reset caption parsing state in the transmuxer
  49161. _this3.tech_.trigger('hls-reset');
  49162. });
  49163. this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
  49164. // If playing an MTS stream in html, a videojs.MediaSource is listening for
  49165. // hls-segment-time-mapping update its internal mapping of stream to display time
  49166. _this3.tech_.trigger({
  49167. type: 'hls-segment-time-mapping',
  49168. mapping: event.mapping
  49169. });
  49170. });
  49171. this.audioSegmentLoader_.on('ended', function () {
  49172. _this3.onEndOfStream();
  49173. });
  49174. }
  49175. }, {
  49176. key: 'mediaSecondsLoaded_',
  49177. value: function mediaSecondsLoaded_() {
  49178. return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
  49179. }
  49180. /**
  49181. * Call load on our SegmentLoaders
  49182. */
  49183. }, {
  49184. key: 'load',
  49185. value: function load() {
  49186. this.mainSegmentLoader_.load();
  49187. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  49188. this.audioSegmentLoader_.load();
  49189. }
  49190. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  49191. this.subtitleSegmentLoader_.load();
  49192. }
  49193. }
  49194. /**
  49195. * Re-tune playback quality level for the current player
  49196. * conditions without performing destructive actions, like
  49197. * removing already buffered content
  49198. *
  49199. * @private
  49200. */
  49201. }, {
  49202. key: 'smoothQualityChange_',
  49203. value: function smoothQualityChange_() {
  49204. var media = this.selectPlaylist();
  49205. if (media !== this.masterPlaylistLoader_.media()) {
  49206. this.masterPlaylistLoader_.media(media);
  49207. this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes
  49208. }
  49209. }
  49210. /**
  49211. * Re-tune playback quality level for the current player
  49212. * conditions. This method will perform destructive actions like removing
  49213. * already buffered content in order to readjust the currently active
  49214. * playlist quickly. This is good for manual quality changes
  49215. *
  49216. * @private
  49217. */
  49218. }, {
  49219. key: 'fastQualityChange_',
  49220. value: function fastQualityChange_() {
  49221. var _this4 = this;
  49222. var media = this.selectPlaylist();
  49223. if (media === this.masterPlaylistLoader_.media()) {
  49224. return;
  49225. }
  49226. this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give
  49227. // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
  49228. // ahead is roughly the minimum that will accomplish this across a variety of content
  49229. // in IE and Edge, but seeking in place is sufficient on all other browsers)
  49230. // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
  49231. // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
  49232. this.mainSegmentLoader_.resetEverything(function () {
  49233. // Since this is not a typical seek, we avoid the seekTo method which can cause segments
  49234. // from the previously enabled rendition to load before the new playlist has finished loading
  49235. if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) {
  49236. _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04);
  49237. } else {
  49238. _this4.tech_.setCurrentTime(_this4.tech_.currentTime());
  49239. }
  49240. }); // don't need to reset audio as it is reset when media changes
  49241. }
  49242. /**
  49243. * Begin playback.
  49244. */
  49245. }, {
  49246. key: 'play',
  49247. value: function play() {
  49248. if (this.setupFirstPlay()) {
  49249. return;
  49250. }
  49251. if (this.tech_.ended()) {
  49252. this.seekTo_(0);
  49253. }
  49254. if (this.hasPlayed_()) {
  49255. this.load();
  49256. }
  49257. var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
  49258. // seek forward to the live point
  49259. if (this.tech_.duration() === Infinity) {
  49260. if (this.tech_.currentTime() < seekable$$1.start(0)) {
  49261. return this.seekTo_(seekable$$1.end(seekable$$1.length - 1));
  49262. }
  49263. }
  49264. }
  49265. /**
  49266. * Seek to the latest media position if this is a live video and the
  49267. * player and video are loaded and initialized.
  49268. */
  49269. }, {
  49270. key: 'setupFirstPlay',
  49271. value: function setupFirstPlay() {
  49272. var _this5 = this;
  49273. var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
  49274. // If 1) there is no active media
  49275. // 2) the player is paused
  49276. // 3) the first play has already been setup
  49277. // then exit early
  49278. if (!media || this.tech_.paused() || this.hasPlayed_()) {
  49279. return false;
  49280. } // when the video is a live stream
  49281. if (!media.endList) {
  49282. var seekable$$1 = this.seekable();
  49283. if (!seekable$$1.length) {
  49284. // without a seekable range, the player cannot seek to begin buffering at the live
  49285. // point
  49286. return false;
  49287. }
  49288. if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
  49289. // IE11 throws an InvalidStateError if you try to set currentTime while the
  49290. // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
  49291. this.tech_.one('loadedmetadata', function () {
  49292. _this5.trigger('firstplay');
  49293. _this5.seekTo_(seekable$$1.end(0));
  49294. _this5.hasPlayed_ = function () {
  49295. return true;
  49296. };
  49297. });
  49298. return false;
  49299. } // trigger firstplay to inform the source handler to ignore the next seek event
  49300. this.trigger('firstplay'); // seek to the live point
  49301. this.seekTo_(seekable$$1.end(0));
  49302. }
  49303. this.hasPlayed_ = function () {
  49304. return true;
  49305. }; // we can begin loading now that everything is ready
  49306. this.load();
  49307. return true;
  49308. }
  49309. /**
  49310. * handle the sourceopen event on the MediaSource
  49311. *
  49312. * @private
  49313. */
  49314. }, {
  49315. key: 'handleSourceOpen_',
  49316. value: function handleSourceOpen_() {
  49317. // Only attempt to create the source buffer if none already exist.
  49318. // handleSourceOpen is also called when we are "re-opening" a source buffer
  49319. // after `endOfStream` has been called (in response to a seek for instance)
  49320. try {
  49321. this.setupSourceBuffers_();
  49322. } catch (e) {
  49323. videojs$1.log.warn('Failed to create Source Buffers', e);
  49324. return this.mediaSource.endOfStream('decode');
  49325. } // if autoplay is enabled, begin playback. This is duplicative of
  49326. // code in video.js but is required because play() must be invoked
  49327. // *after* the media source has opened.
  49328. if (this.tech_.autoplay()) {
  49329. var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
  49330. // on browsers which return a promise
  49331. if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
  49332. playPromise.then(null, function (e) {});
  49333. }
  49334. }
  49335. this.trigger('sourceopen');
  49336. }
  49337. /**
  49338. * Calls endOfStream on the media source when all active stream types have called
  49339. * endOfStream
  49340. *
  49341. * @param {string} streamType
  49342. * Stream type of the segment loader that called endOfStream
  49343. * @private
  49344. */
  49345. }, {
  49346. key: 'onEndOfStream',
  49347. value: function onEndOfStream() {
  49348. var isEndOfStream = this.mainSegmentLoader_.ended_;
  49349. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  49350. // if the audio playlist loader exists, then alternate audio is active
  49351. if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
  49352. // if we do not know if the main segment loader contains video yet or if we
  49353. // definitively know the main segment loader contains video, then we need to wait
  49354. // for both main and audio segment loaders to call endOfStream
  49355. isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
  49356. } else {
  49357. // otherwise just rely on the audio loader
  49358. isEndOfStream = this.audioSegmentLoader_.ended_;
  49359. }
  49360. }
  49361. if (!isEndOfStream) {
  49362. return;
  49363. }
  49364. this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception,
  49365. // even when the media source is in a valid state.
  49366. try {
  49367. this.mediaSource.endOfStream();
  49368. } catch (e) {
  49369. videojs$1.log.warn('Failed to call media source endOfStream', e);
  49370. }
  49371. }
  49372. /**
  49373. * Check if a playlist has stopped being updated
  49374. * @param {Object} playlist the media playlist object
  49375. * @return {boolean} whether the playlist has stopped being updated or not
  49376. */
  49377. }, {
  49378. key: 'stuckAtPlaylistEnd_',
  49379. value: function stuckAtPlaylistEnd_(playlist) {
  49380. var seekable$$1 = this.seekable();
  49381. if (!seekable$$1.length) {
  49382. // playlist doesn't have enough information to determine whether we are stuck
  49383. return false;
  49384. }
  49385. var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
  49386. if (expired === null) {
  49387. return false;
  49388. } // does not use the safe live end to calculate playlist end, since we
  49389. // don't want to say we are stuck while there is still content
  49390. var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
  49391. var currentTime = this.tech_.currentTime();
  49392. var buffered = this.tech_.buffered();
  49393. if (!buffered.length) {
  49394. // return true if the playhead reached the absolute end of the playlist
  49395. return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
  49396. }
  49397. var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
  49398. // end of playlist
  49399. return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
  49400. }
  49401. /**
  49402. * Blacklists a playlist when an error occurs for a set amount of time
  49403. * making it unavailable for selection by the rendition selection algorithm
  49404. * and then forces a new playlist (rendition) selection.
  49405. *
  49406. * @param {Object=} error an optional error that may include the playlist
  49407. * to blacklist
  49408. * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
  49409. * playlist
  49410. */
  49411. }, {
  49412. key: 'blacklistCurrentPlaylist',
  49413. value: function blacklistCurrentPlaylist() {
  49414. var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  49415. var blacklistDuration = arguments[1];
  49416. var currentPlaylist = void 0;
  49417. var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain
  49418. // the playlist we were trying to load (but failed) and that should be
  49419. // blacklisted instead of the currently selected playlist which is likely
  49420. // out-of-date in this scenario
  49421. currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
  49422. blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
  49423. // trying to load the master OR while we were disposing of the tech
  49424. if (!currentPlaylist) {
  49425. this.error = error;
  49426. try {
  49427. return this.mediaSource.endOfStream('network');
  49428. } catch (e) {
  49429. return this.trigger('error');
  49430. }
  49431. }
  49432. var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
  49433. var playlists = this.masterPlaylistLoader_.master.playlists;
  49434. if (playlists.length === 1) {
  49435. // Never blacklisting this playlist because it's the only playlist
  49436. videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the only playlist.');
  49437. this.tech_.trigger('retryplaylist');
  49438. return this.masterPlaylistLoader_.load(isFinalRendition);
  49439. }
  49440. if (isFinalRendition) {
  49441. // Since we're on the final non-blacklisted playlist, and we're about to blacklist
  49442. // it, instead of erring the player or retrying this playlist, clear out the current
  49443. // blacklist. This allows other playlists to be attempted in case any have been
  49444. // fixed.
  49445. videojs$1.log.warn('Removing all playlists from the blacklist because the last ' + 'rendition is about to be blacklisted.');
  49446. playlists.forEach(function (playlist) {
  49447. if (playlist.excludeUntil !== Infinity) {
  49448. delete playlist.excludeUntil;
  49449. }
  49450. }); // Technically we are retrying a playlist, in that we are simply retrying a previous
  49451. // playlist. This is needed for users relying on the retryplaylist event to catch a
  49452. // case where the player might be stuck and looping through "dead" playlists.
  49453. this.tech_.trigger('retryplaylist');
  49454. } // Blacklist this playlist
  49455. currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
  49456. this.tech_.trigger('blacklistplaylist');
  49457. this.tech_.trigger({
  49458. type: 'usage',
  49459. name: 'hls-rendition-blacklisted'
  49460. }); // Select a new playlist
  49461. nextPlaylist = this.selectPlaylist();
  49462. videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
  49463. return this.masterPlaylistLoader_.media(nextPlaylist, isFinalRendition);
  49464. }
  49465. /**
  49466. * Pause all segment loaders
  49467. */
  49468. }, {
  49469. key: 'pauseLoading',
  49470. value: function pauseLoading() {
  49471. this.mainSegmentLoader_.pause();
  49472. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  49473. this.audioSegmentLoader_.pause();
  49474. }
  49475. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  49476. this.subtitleSegmentLoader_.pause();
  49477. }
  49478. }
  49479. /**
  49480. * set the current time on all segment loaders
  49481. *
  49482. * @param {TimeRange} currentTime the current time to set
  49483. * @return {TimeRange} the current time
  49484. */
  49485. }, {
  49486. key: 'setCurrentTime',
  49487. value: function setCurrentTime(currentTime) {
  49488. var buffered = findRange(this.tech_.buffered(), currentTime);
  49489. if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
  49490. // return immediately if the metadata is not ready yet
  49491. return 0;
  49492. } // it's clearly an edge-case but don't thrown an error if asked to
  49493. // seek within an empty playlist
  49494. if (!this.masterPlaylistLoader_.media().segments) {
  49495. return 0;
  49496. } // In flash playback, the segment loaders should be reset on every seek, even
  49497. // in buffer seeks. If the seek location is already buffered, continue buffering as
  49498. // usual
  49499. // TODO: redo this comment
  49500. if (buffered && buffered.length) {
  49501. return currentTime;
  49502. } // cancel outstanding requests so we begin buffering at the new
  49503. // location
  49504. this.mainSegmentLoader_.resetEverything();
  49505. this.mainSegmentLoader_.abort();
  49506. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  49507. this.audioSegmentLoader_.resetEverything();
  49508. this.audioSegmentLoader_.abort();
  49509. }
  49510. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  49511. this.subtitleSegmentLoader_.resetEverything();
  49512. this.subtitleSegmentLoader_.abort();
  49513. } // start segment loader loading in case they are paused
  49514. this.load();
  49515. }
  49516. /**
  49517. * get the current duration
  49518. *
  49519. * @return {TimeRange} the duration
  49520. */
  49521. }, {
  49522. key: 'duration',
  49523. value: function duration$$1() {
  49524. if (!this.masterPlaylistLoader_) {
  49525. return 0;
  49526. }
  49527. if (this.mediaSource) {
  49528. return this.mediaSource.duration;
  49529. }
  49530. return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  49531. }
  49532. /**
  49533. * check the seekable range
  49534. *
  49535. * @return {TimeRange} the seekable range
  49536. */
  49537. }, {
  49538. key: 'seekable',
  49539. value: function seekable$$1() {
  49540. return this.seekable_;
  49541. }
  49542. }, {
  49543. key: 'onSyncInfoUpdate_',
  49544. value: function onSyncInfoUpdate_() {
  49545. var mainSeekable = void 0;
  49546. var audioSeekable = void 0;
  49547. if (!this.masterPlaylistLoader_) {
  49548. return;
  49549. }
  49550. var media = this.masterPlaylistLoader_.media();
  49551. if (!media) {
  49552. return;
  49553. }
  49554. var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  49555. if (expired === null) {
  49556. // not enough information to update seekable
  49557. return;
  49558. }
  49559. mainSeekable = Hls.Playlist.seekable(media, expired);
  49560. if (mainSeekable.length === 0) {
  49561. return;
  49562. }
  49563. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  49564. media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
  49565. expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  49566. if (expired === null) {
  49567. return;
  49568. }
  49569. audioSeekable = Hls.Playlist.seekable(media, expired);
  49570. if (audioSeekable.length === 0) {
  49571. return;
  49572. }
  49573. }
  49574. var oldEnd = void 0;
  49575. var oldStart = void 0;
  49576. if (this.seekable_ && this.seekable_.length) {
  49577. oldEnd = this.seekable_.end(0);
  49578. oldStart = this.seekable_.start(0);
  49579. }
  49580. if (!audioSeekable) {
  49581. // seekable has been calculated based on buffering video data so it
  49582. // can be returned directly
  49583. this.seekable_ = mainSeekable;
  49584. } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
  49585. // seekables are pretty far off, rely on main
  49586. this.seekable_ = mainSeekable;
  49587. } else {
  49588. 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)]]);
  49589. } // seekable is the same as last time
  49590. if (this.seekable_ && this.seekable_.length) {
  49591. if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
  49592. return;
  49593. }
  49594. }
  49595. this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
  49596. this.tech_.trigger('seekablechanged');
  49597. }
  49598. /**
  49599. * Update the player duration
  49600. */
  49601. }, {
  49602. key: 'updateDuration',
  49603. value: function updateDuration() {
  49604. var _this6 = this;
  49605. var oldDuration = this.mediaSource.duration;
  49606. var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  49607. var buffered = this.tech_.buffered();
  49608. var setDuration = function setDuration() {
  49609. // on firefox setting the duration may sometimes cause an exception
  49610. // even if the media source is open and source buffers are not
  49611. // updating, something about the media source being in an invalid state.
  49612. _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration);
  49613. try {
  49614. _this6.mediaSource.duration = newDuration;
  49615. } catch (e) {
  49616. videojs$1.log.warn('Failed to set media source duration', e);
  49617. }
  49618. _this6.tech_.trigger('durationchange');
  49619. _this6.mediaSource.removeEventListener('sourceopen', setDuration);
  49620. };
  49621. if (buffered.length > 0) {
  49622. newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
  49623. } // if the duration has changed, invalidate the cached value
  49624. if (oldDuration !== newDuration) {
  49625. // update the duration
  49626. if (this.mediaSource.readyState !== 'open') {
  49627. this.mediaSource.addEventListener('sourceopen', setDuration);
  49628. } else {
  49629. setDuration();
  49630. }
  49631. }
  49632. }
  49633. /**
  49634. * dispose of the MasterPlaylistController and everything
  49635. * that it controls
  49636. */
  49637. }, {
  49638. key: 'dispose',
  49639. value: function dispose() {
  49640. var _this7 = this;
  49641. this.decrypter_.terminate();
  49642. this.masterPlaylistLoader_.dispose();
  49643. this.mainSegmentLoader_.dispose();
  49644. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  49645. var groups = _this7.mediaTypes_[type].groups;
  49646. for (var id in groups) {
  49647. groups[id].forEach(function (group) {
  49648. if (group.playlistLoader) {
  49649. group.playlistLoader.dispose();
  49650. }
  49651. });
  49652. }
  49653. });
  49654. this.audioSegmentLoader_.dispose();
  49655. this.subtitleSegmentLoader_.dispose();
  49656. }
  49657. /**
  49658. * return the master playlist object if we have one
  49659. *
  49660. * @return {Object} the master playlist object that we parsed
  49661. */
  49662. }, {
  49663. key: 'master',
  49664. value: function master() {
  49665. return this.masterPlaylistLoader_.master;
  49666. }
  49667. /**
  49668. * return the currently selected playlist
  49669. *
  49670. * @return {Object} the currently selected playlist object that we parsed
  49671. */
  49672. }, {
  49673. key: 'media',
  49674. value: function media() {
  49675. // playlist loader will not return media if it has not been fully loaded
  49676. return this.masterPlaylistLoader_.media() || this.initialMedia_;
  49677. }
  49678. /**
  49679. * setup our internal source buffers on our segment Loaders
  49680. *
  49681. * @private
  49682. */
  49683. }, {
  49684. key: 'setupSourceBuffers_',
  49685. value: function setupSourceBuffers_() {
  49686. var media = this.masterPlaylistLoader_.media();
  49687. var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is
  49688. // attached
  49689. if (!media || this.mediaSource.readyState !== 'open') {
  49690. return;
  49691. }
  49692. mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
  49693. if (mimeTypes.length < 1) {
  49694. this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
  49695. return this.mediaSource.endOfStream('decode');
  49696. }
  49697. this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist
  49698. // selection
  49699. this.excludeIncompatibleVariants_(media);
  49700. }
  49701. }, {
  49702. key: 'configureLoaderMimeTypes_',
  49703. value: function configureLoaderMimeTypes_(mimeTypes) {
  49704. // If the content is demuxed, we can't start appending segments to a source buffer
  49705. // until both source buffers are set up, or else the browser may not let us add the
  49706. // second source buffer (it will assume we are playing either audio only or video
  49707. // only).
  49708. var sourceBufferEmitter = // If there is more than one mime type
  49709. mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio
  49710. mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio
  49711. // only with alternate audio)
  49712. mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer
  49713. new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only,
  49714. // video only, or muxed content.
  49715. null;
  49716. this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
  49717. if (mimeTypes[1]) {
  49718. this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
  49719. }
  49720. }
  49721. /**
  49722. * Blacklists playlists with codecs that are unsupported by the browser.
  49723. */
  49724. }, {
  49725. key: 'excludeUnsupportedVariants_',
  49726. value: function excludeUnsupportedVariants_() {
  49727. this.master().playlists.forEach(function (variant) {
  49728. if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
  49729. variant.excludeUntil = Infinity;
  49730. }
  49731. });
  49732. }
  49733. /**
  49734. * Blacklist playlists that are known to be codec or
  49735. * stream-incompatible with the SourceBuffer configuration. For
  49736. * instance, Media Source Extensions would cause the video element to
  49737. * stall waiting for video data if you switched from a variant with
  49738. * video and audio to an audio-only one.
  49739. *
  49740. * @param {Object} media a media playlist compatible with the current
  49741. * set of SourceBuffers. Variants in the current master playlist that
  49742. * do not appear to have compatible codec or stream configurations
  49743. * will be excluded from the default playlist selection algorithm
  49744. * indefinitely.
  49745. * @private
  49746. */
  49747. }, {
  49748. key: 'excludeIncompatibleVariants_',
  49749. value: function excludeIncompatibleVariants_(media) {
  49750. var codecCount = 2;
  49751. var videoCodec = null;
  49752. var codecs = void 0;
  49753. if (media.attributes.CODECS) {
  49754. codecs = parseCodecs(media.attributes.CODECS);
  49755. videoCodec = codecs.videoCodec;
  49756. codecCount = codecs.codecCount;
  49757. }
  49758. this.master().playlists.forEach(function (variant) {
  49759. var variantCodecs = {
  49760. codecCount: 2,
  49761. videoCodec: null
  49762. };
  49763. if (variant.attributes.CODECS) {
  49764. variantCodecs = parseCodecs(variant.attributes.CODECS);
  49765. } // if the streams differ in the presence or absence of audio or
  49766. // video, they are incompatible
  49767. if (variantCodecs.codecCount !== codecCount) {
  49768. variant.excludeUntil = Infinity;
  49769. } // if h.264 is specified on the current playlist, some flavor of
  49770. // it must be specified on all compatible variants
  49771. if (variantCodecs.videoCodec !== videoCodec) {
  49772. variant.excludeUntil = Infinity;
  49773. }
  49774. });
  49775. }
  49776. }, {
  49777. key: 'updateAdCues_',
  49778. value: function updateAdCues_(media) {
  49779. var offset = 0;
  49780. var seekable$$1 = this.seekable();
  49781. if (seekable$$1.length) {
  49782. offset = seekable$$1.start(0);
  49783. }
  49784. updateAdCues(media, this.cueTagsTrack_, offset);
  49785. }
  49786. /**
  49787. * Calculates the desired forward buffer length based on current time
  49788. *
  49789. * @return {Number} Desired forward buffer length in seconds
  49790. */
  49791. }, {
  49792. key: 'goalBufferLength',
  49793. value: function goalBufferLength() {
  49794. var currentTime = this.tech_.currentTime();
  49795. var initial = Config.GOAL_BUFFER_LENGTH;
  49796. var rate = Config.GOAL_BUFFER_LENGTH_RATE;
  49797. var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
  49798. return Math.min(initial + currentTime * rate, max);
  49799. }
  49800. /**
  49801. * Calculates the desired buffer low water line based on current time
  49802. *
  49803. * @return {Number} Desired buffer low water line in seconds
  49804. */
  49805. }, {
  49806. key: 'bufferLowWaterLine',
  49807. value: function bufferLowWaterLine() {
  49808. var currentTime = this.tech_.currentTime();
  49809. var initial = Config.BUFFER_LOW_WATER_LINE;
  49810. var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
  49811. var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
  49812. return Math.min(initial + currentTime * rate, max);
  49813. }
  49814. }]);
  49815. return MasterPlaylistController;
  49816. }(videojs$1.EventTarget);
  49817. /**
  49818. * Returns a function that acts as the Enable/disable playlist function.
  49819. *
  49820. * @param {PlaylistLoader} loader - The master playlist loader
  49821. * @param {String} playlistUri - uri of the playlist
  49822. * @param {Function} changePlaylistFn - A function to be called after a
  49823. * playlist's enabled-state has been changed. Will NOT be called if a
  49824. * playlist's enabled-state is unchanged
  49825. * @param {Boolean=} enable - Value to set the playlist enabled-state to
  49826. * or if undefined returns the current enabled-state for the playlist
  49827. * @return {Function} Function for setting/getting enabled
  49828. */
  49829. var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
  49830. return function (enable) {
  49831. var playlist = loader.master.playlists[playlistUri];
  49832. var incompatible = isIncompatible(playlist);
  49833. var currentlyEnabled = isEnabled(playlist);
  49834. if (typeof enable === 'undefined') {
  49835. return currentlyEnabled;
  49836. }
  49837. if (enable) {
  49838. delete playlist.disabled;
  49839. } else {
  49840. playlist.disabled = true;
  49841. }
  49842. if (enable !== currentlyEnabled && !incompatible) {
  49843. // Ensure the outside world knows about our changes
  49844. changePlaylistFn();
  49845. if (enable) {
  49846. loader.trigger('renditionenabled');
  49847. } else {
  49848. loader.trigger('renditiondisabled');
  49849. }
  49850. }
  49851. return enable;
  49852. };
  49853. };
  49854. /**
  49855. * The representation object encapsulates the publicly visible information
  49856. * in a media playlist along with a setter/getter-type function (enabled)
  49857. * for changing the enabled-state of a particular playlist entry
  49858. *
  49859. * @class Representation
  49860. */
  49861. var Representation = function Representation(hlsHandler, playlist, id) {
  49862. classCallCheck$1(this, Representation);
  49863. var mpc = hlsHandler.masterPlaylistController_,
  49864. smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
  49865. var changeType = smoothQualityChange ? 'smooth' : 'fast';
  49866. var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional
  49867. if (playlist.attributes.RESOLUTION) {
  49868. var resolution = playlist.attributes.RESOLUTION;
  49869. this.width = resolution.width;
  49870. this.height = resolution.height;
  49871. }
  49872. this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist
  49873. // within the master playlist
  49874. this.id = id; // Partially-apply the enableFunction to create a playlist-
  49875. // specific variant
  49876. this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction);
  49877. };
  49878. /**
  49879. * A mixin function that adds the `representations` api to an instance
  49880. * of the HlsHandler class
  49881. * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
  49882. * representation API into
  49883. */
  49884. var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
  49885. var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance
  49886. hlsHandler.representations = function () {
  49887. return playlists.master.playlists.filter(function (media) {
  49888. return !isIncompatible(media);
  49889. }).map(function (e, i) {
  49890. return new Representation(hlsHandler, e, e.uri);
  49891. });
  49892. };
  49893. };
  49894. /**
  49895. * @file playback-watcher.js
  49896. *
  49897. * Playback starts, and now my watch begins. It shall not end until my death. I shall
  49898. * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
  49899. * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
  49900. * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
  49901. * my life and honor to the Playback Watch, for this Player and all the Players to come.
  49902. */
  49903. // Set of events that reset the playback-watcher time check logic and clear the timeout
  49904. var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
  49905. /**
  49906. * @class PlaybackWatcher
  49907. */
  49908. var PlaybackWatcher = function () {
  49909. /**
  49910. * Represents an PlaybackWatcher object.
  49911. * @constructor
  49912. * @param {object} options an object that includes the tech and settings
  49913. */
  49914. function PlaybackWatcher(options) {
  49915. var _this = this;
  49916. classCallCheck$1(this, PlaybackWatcher);
  49917. this.tech_ = options.tech;
  49918. this.seekable = options.seekable;
  49919. this.seekTo = options.seekTo;
  49920. this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
  49921. this.media = options.media;
  49922. this.consecutiveUpdates = 0;
  49923. this.lastRecordedTime = null;
  49924. this.timer_ = null;
  49925. this.checkCurrentTimeTimeout_ = null;
  49926. this.logger_ = logger('PlaybackWatcher');
  49927. this.logger_('initialize');
  49928. var canPlayHandler = function canPlayHandler() {
  49929. return _this.monitorCurrentTime_();
  49930. };
  49931. var waitingHandler = function waitingHandler() {
  49932. return _this.techWaiting_();
  49933. };
  49934. var cancelTimerHandler = function cancelTimerHandler() {
  49935. return _this.cancelTimer_();
  49936. };
  49937. var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
  49938. return _this.fixesBadSeeks_();
  49939. };
  49940. this.tech_.on('seekablechanged', fixesBadSeeksHandler);
  49941. this.tech_.on('waiting', waitingHandler);
  49942. this.tech_.on(timerCancelEvents, cancelTimerHandler);
  49943. this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events
  49944. this.dispose = function () {
  49945. _this.logger_('dispose');
  49946. _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
  49947. _this.tech_.off('waiting', waitingHandler);
  49948. _this.tech_.off(timerCancelEvents, cancelTimerHandler);
  49949. _this.tech_.off('canplay', canPlayHandler);
  49950. if (_this.checkCurrentTimeTimeout_) {
  49951. window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
  49952. }
  49953. _this.cancelTimer_();
  49954. };
  49955. }
  49956. /**
  49957. * Periodically check current time to see if playback stopped
  49958. *
  49959. * @private
  49960. */
  49961. createClass$1(PlaybackWatcher, [{
  49962. key: 'monitorCurrentTime_',
  49963. value: function monitorCurrentTime_() {
  49964. this.checkCurrentTime_();
  49965. if (this.checkCurrentTimeTimeout_) {
  49966. window$1.clearTimeout(this.checkCurrentTimeTimeout_);
  49967. } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  49968. this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
  49969. }
  49970. /**
  49971. * The purpose of this function is to emulate the "waiting" event on
  49972. * browsers that do not emit it when they are waiting for more
  49973. * data to continue playback
  49974. *
  49975. * @private
  49976. */
  49977. }, {
  49978. key: 'checkCurrentTime_',
  49979. value: function checkCurrentTime_() {
  49980. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  49981. this.consecutiveUpdates = 0;
  49982. this.lastRecordedTime = this.tech_.currentTime();
  49983. return;
  49984. }
  49985. if (this.tech_.paused() || this.tech_.seeking()) {
  49986. return;
  49987. }
  49988. var currentTime = this.tech_.currentTime();
  49989. var buffered = this.tech_.buffered();
  49990. if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
  49991. // If current time is at the end of the final buffered region, then any playback
  49992. // stall is most likely caused by buffering in a low bandwidth environment. The tech
  49993. // should fire a `waiting` event in this scenario, but due to browser and tech
  49994. // inconsistencies. Calling `techWaiting_` here allows us to simulate
  49995. // responding to a native `waiting` event when the tech fails to emit one.
  49996. return this.techWaiting_();
  49997. }
  49998. if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
  49999. this.consecutiveUpdates++;
  50000. this.waiting_();
  50001. } else if (currentTime === this.lastRecordedTime) {
  50002. this.consecutiveUpdates++;
  50003. } else {
  50004. this.consecutiveUpdates = 0;
  50005. this.lastRecordedTime = currentTime;
  50006. }
  50007. }
  50008. /**
  50009. * Cancels any pending timers and resets the 'timeupdate' mechanism
  50010. * designed to detect that we are stalled
  50011. *
  50012. * @private
  50013. */
  50014. }, {
  50015. key: 'cancelTimer_',
  50016. value: function cancelTimer_() {
  50017. this.consecutiveUpdates = 0;
  50018. if (this.timer_) {
  50019. this.logger_('cancelTimer_');
  50020. clearTimeout(this.timer_);
  50021. }
  50022. this.timer_ = null;
  50023. }
  50024. /**
  50025. * Fixes situations where there's a bad seek
  50026. *
  50027. * @return {Boolean} whether an action was taken to fix the seek
  50028. * @private
  50029. */
  50030. }, {
  50031. key: 'fixesBadSeeks_',
  50032. value: function fixesBadSeeks_() {
  50033. var seeking = this.tech_.seeking();
  50034. if (!seeking) {
  50035. return false;
  50036. }
  50037. var seekable = this.seekable();
  50038. var currentTime = this.tech_.currentTime();
  50039. var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
  50040. var seekTo = void 0;
  50041. if (isAfterSeekableRange) {
  50042. var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
  50043. seekTo = seekableEnd;
  50044. }
  50045. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  50046. var seekableStart = seekable.start(0); // sync to the beginning of the live window
  50047. // provide a buffer of .1 seconds to handle rounding/imprecise numbers
  50048. seekTo = seekableStart + SAFE_TIME_DELTA;
  50049. }
  50050. if (typeof seekTo !== 'undefined') {
  50051. this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
  50052. this.seekTo(seekTo);
  50053. return true;
  50054. }
  50055. return false;
  50056. }
  50057. /**
  50058. * Handler for situations when we determine the player is waiting.
  50059. *
  50060. * @private
  50061. */
  50062. }, {
  50063. key: 'waiting_',
  50064. value: function waiting_() {
  50065. if (this.techWaiting_()) {
  50066. return;
  50067. } // All tech waiting checks failed. Use last resort correction
  50068. var currentTime = this.tech_.currentTime();
  50069. var buffered = this.tech_.buffered();
  50070. var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
  50071. // region with no indication that anything is amiss (seen in Firefox). Seeking to
  50072. // currentTime is usually enough to kickstart the player. This checks that the player
  50073. // is currently within a buffered region before attempting a corrective seek.
  50074. // Chrome does not appear to continue `timeupdate` events after a `waiting` event
  50075. // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
  50076. // make sure there is ~3 seconds of forward buffer before taking any corrective action
  50077. // to avoid triggering an `unknownwaiting` event when the network is slow.
  50078. if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
  50079. this.cancelTimer_();
  50080. this.seekTo(currentTime);
  50081. 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
  50082. this.tech_.trigger({
  50083. type: 'usage',
  50084. name: 'hls-unknown-waiting'
  50085. });
  50086. return;
  50087. }
  50088. }
  50089. /**
  50090. * Handler for situations when the tech fires a `waiting` event
  50091. *
  50092. * @return {Boolean}
  50093. * True if an action (or none) was needed to correct the waiting. False if no
  50094. * checks passed
  50095. * @private
  50096. */
  50097. }, {
  50098. key: 'techWaiting_',
  50099. value: function techWaiting_() {
  50100. var seekable = this.seekable();
  50101. var currentTime = this.tech_.currentTime();
  50102. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  50103. // Tech is seeking or bad seek fixed, no action needed
  50104. return true;
  50105. }
  50106. if (this.tech_.seeking() || this.timer_ !== null) {
  50107. // Tech is seeking or already waiting on another action, no action needed
  50108. return true;
  50109. }
  50110. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  50111. var livePoint = seekable.end(seekable.length - 1);
  50112. this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
  50113. this.cancelTimer_();
  50114. this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS
  50115. this.tech_.trigger({
  50116. type: 'usage',
  50117. name: 'hls-live-resync'
  50118. });
  50119. return true;
  50120. }
  50121. var buffered = this.tech_.buffered();
  50122. var nextRange = findNextRange(buffered, currentTime);
  50123. if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
  50124. // Even though the video underflowed and was stuck in a gap, the audio overplayed
  50125. // the gap, leading currentTime into a buffered range. Seeking to currentTime
  50126. // allows the video to catch up to the audio position without losing any audio
  50127. // (only suffering ~3 seconds of frozen video and a pause in audio playback).
  50128. this.cancelTimer_();
  50129. this.seekTo(currentTime); // video underflow may be useful for monitoring QoS
  50130. this.tech_.trigger({
  50131. type: 'usage',
  50132. name: 'hls-video-underflow'
  50133. });
  50134. return true;
  50135. } // check for gap
  50136. if (nextRange.length > 0) {
  50137. var difference = nextRange.start(0) - currentTime;
  50138. this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
  50139. this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
  50140. return true;
  50141. } // All checks failed. Returning false to indicate failure to correct waiting
  50142. return false;
  50143. }
  50144. }, {
  50145. key: 'afterSeekableWindow_',
  50146. value: function afterSeekableWindow_(seekable, currentTime, playlist) {
  50147. var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  50148. if (!seekable.length) {
  50149. // we can't make a solid case if there's no seekable, default to false
  50150. return false;
  50151. }
  50152. var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
  50153. var isLive = !playlist.endList;
  50154. if (isLive && allowSeeksWithinUnsafeLiveWindow) {
  50155. allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
  50156. }
  50157. if (currentTime > allowedEnd) {
  50158. return true;
  50159. }
  50160. return false;
  50161. }
  50162. }, {
  50163. key: 'beforeSeekableWindow_',
  50164. value: function beforeSeekableWindow_(seekable, currentTime) {
  50165. if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
  50166. seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
  50167. return true;
  50168. }
  50169. return false;
  50170. }
  50171. }, {
  50172. key: 'videoUnderflow_',
  50173. value: function videoUnderflow_(nextRange, buffered, currentTime) {
  50174. if (nextRange.length === 0) {
  50175. // Even if there is no available next range, there is still a possibility we are
  50176. // stuck in a gap due to video underflow.
  50177. var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
  50178. if (gap) {
  50179. this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
  50180. return true;
  50181. }
  50182. }
  50183. return false;
  50184. }
  50185. /**
  50186. * Timer callback. If playback still has not proceeded, then we seek
  50187. * to the start of the next buffered region.
  50188. *
  50189. * @private
  50190. */
  50191. }, {
  50192. key: 'skipTheGap_',
  50193. value: function skipTheGap_(scheduledCurrentTime) {
  50194. var buffered = this.tech_.buffered();
  50195. var currentTime = this.tech_.currentTime();
  50196. var nextRange = findNextRange(buffered, currentTime);
  50197. this.cancelTimer_();
  50198. if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
  50199. return;
  50200. }
  50201. this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
  50202. this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR);
  50203. this.tech_.trigger({
  50204. type: 'usage',
  50205. name: 'hls-gap-skip'
  50206. });
  50207. }
  50208. }, {
  50209. key: 'gapFromVideoUnderflow_',
  50210. value: function gapFromVideoUnderflow_(buffered, currentTime) {
  50211. // At least in Chrome, if there is a gap in the video buffer, the audio will continue
  50212. // playing for ~3 seconds after the video gap starts. This is done to account for
  50213. // video buffer underflow/underrun (note that this is not done when there is audio
  50214. // buffer underflow/underrun -- in that case the video will stop as soon as it
  50215. // encounters the gap, as audio stalls are more noticeable/jarring to a user than
  50216. // video stalls). The player's time will reflect the playthrough of audio, so the
  50217. // time will appear as if we are in a buffered region, even if we are stuck in a
  50218. // "gap."
  50219. //
  50220. // Example:
  50221. // video buffer: 0 => 10.1, 10.2 => 20
  50222. // audio buffer: 0 => 20
  50223. // overall buffer: 0 => 10.1, 10.2 => 20
  50224. // current time: 13
  50225. //
  50226. // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
  50227. // however, the audio continued playing until it reached ~3 seconds past the gap
  50228. // (13 seconds), at which point it stops as well. Since current time is past the
  50229. // gap, findNextRange will return no ranges.
  50230. //
  50231. // To check for this issue, we see if there is a gap that starts somewhere within
  50232. // a 3 second range (3 seconds +/- 1 second) back from our current time.
  50233. var gaps = findGaps(buffered);
  50234. for (var i = 0; i < gaps.length; i++) {
  50235. var start = gaps.start(i);
  50236. var end = gaps.end(i); // gap is starts no more than 4 seconds back
  50237. if (currentTime - start < 4 && currentTime - start > 2) {
  50238. return {
  50239. start: start,
  50240. end: end
  50241. };
  50242. }
  50243. }
  50244. return null;
  50245. }
  50246. }]);
  50247. return PlaybackWatcher;
  50248. }();
  50249. var defaultOptions = {
  50250. errorInterval: 30,
  50251. getSource: function getSource(next) {
  50252. var tech = this.tech({
  50253. IWillNotUseThisInPlugins: true
  50254. });
  50255. var sourceObj = tech.currentSource_;
  50256. return next(sourceObj);
  50257. }
  50258. };
  50259. /**
  50260. * Main entry point for the plugin
  50261. *
  50262. * @param {Player} player a reference to a videojs Player instance
  50263. * @param {Object} [options] an object with plugin options
  50264. * @private
  50265. */
  50266. var initPlugin = function initPlugin(player, options) {
  50267. var lastCalled = 0;
  50268. var seekTo = 0;
  50269. var localOptions = videojs$1.mergeOptions(defaultOptions, options);
  50270. player.ready(function () {
  50271. player.trigger({
  50272. type: 'usage',
  50273. name: 'hls-error-reload-initialized'
  50274. });
  50275. });
  50276. /**
  50277. * Player modifications to perform that must wait until `loadedmetadata`
  50278. * has been triggered
  50279. *
  50280. * @private
  50281. */
  50282. var loadedMetadataHandler = function loadedMetadataHandler() {
  50283. if (seekTo) {
  50284. player.currentTime(seekTo);
  50285. }
  50286. };
  50287. /**
  50288. * Set the source on the player element, play, and seek if necessary
  50289. *
  50290. * @param {Object} sourceObj An object specifying the source url and mime-type to play
  50291. * @private
  50292. */
  50293. var setSource = function setSource(sourceObj) {
  50294. if (sourceObj === null || sourceObj === undefined) {
  50295. return;
  50296. }
  50297. seekTo = player.duration() !== Infinity && player.currentTime() || 0;
  50298. player.one('loadedmetadata', loadedMetadataHandler);
  50299. player.src(sourceObj);
  50300. player.trigger({
  50301. type: 'usage',
  50302. name: 'hls-error-reload'
  50303. });
  50304. player.play();
  50305. };
  50306. /**
  50307. * Attempt to get a source from either the built-in getSource function
  50308. * or a custom function provided via the options
  50309. *
  50310. * @private
  50311. */
  50312. var errorHandler = function errorHandler() {
  50313. // Do not attempt to reload the source if a source-reload occurred before
  50314. // 'errorInterval' time has elapsed since the last source-reload
  50315. if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
  50316. player.trigger({
  50317. type: 'usage',
  50318. name: 'hls-error-reload-canceled'
  50319. });
  50320. return;
  50321. }
  50322. if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
  50323. videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
  50324. return;
  50325. }
  50326. lastCalled = Date.now();
  50327. return localOptions.getSource.call(player, setSource);
  50328. };
  50329. /**
  50330. * Unbind any event handlers that were bound by the plugin
  50331. *
  50332. * @private
  50333. */
  50334. var cleanupEvents = function cleanupEvents() {
  50335. player.off('loadedmetadata', loadedMetadataHandler);
  50336. player.off('error', errorHandler);
  50337. player.off('dispose', cleanupEvents);
  50338. };
  50339. /**
  50340. * Cleanup before re-initializing the plugin
  50341. *
  50342. * @param {Object} [newOptions] an object with plugin options
  50343. * @private
  50344. */
  50345. var reinitPlugin = function reinitPlugin(newOptions) {
  50346. cleanupEvents();
  50347. initPlugin(player, newOptions);
  50348. };
  50349. player.on('error', errorHandler);
  50350. player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
  50351. // initializing the plugin
  50352. player.reloadSourceOnError = reinitPlugin;
  50353. };
  50354. /**
  50355. * Reload the source when an error is detected as long as there
  50356. * wasn't an error previously within the last 30 seconds
  50357. *
  50358. * @param {Object} [options] an object with plugin options
  50359. */
  50360. var reloadSourceOnError = function reloadSourceOnError(options) {
  50361. initPlugin(this, options);
  50362. };
  50363. var version$1 = "1.10.3"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all
  50364. videojs$1.use('*', function (player) {
  50365. return {
  50366. setSource: function setSource(srcObj, next) {
  50367. // pass null as the first argument to indicate that the source is not rejected
  50368. next(null, srcObj);
  50369. },
  50370. // VHS needs to know when seeks happen. For external seeks (generated at the player
  50371. // level), this middleware will capture the action. For internal seeks (generated at
  50372. // the tech level), we use a wrapped function so that we can handle it on our own
  50373. // (specified elsewhere).
  50374. setCurrentTime: function setCurrentTime(time) {
  50375. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  50376. player.vhs.setCurrentTime(time);
  50377. }
  50378. return time;
  50379. },
  50380. // Sync VHS after play requests.
  50381. // This specifically handles replay where the order of actions is
  50382. // play, video element will seek to 0 (skipping the setCurrentTime middleware)
  50383. // then triggers a play event.
  50384. play: function play() {
  50385. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  50386. player.vhs.setCurrentTime(player.tech_.currentTime());
  50387. }
  50388. }
  50389. };
  50390. });
  50391. /**
  50392. * @file videojs-http-streaming.js
  50393. *
  50394. * The main file for the HLS project.
  50395. * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
  50396. */
  50397. var Hls$1 = {
  50398. PlaylistLoader: PlaylistLoader,
  50399. Playlist: Playlist,
  50400. Decrypter: Decrypter,
  50401. AsyncStream: AsyncStream,
  50402. decrypt: decrypt,
  50403. utils: utils$1,
  50404. STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
  50405. INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
  50406. comparePlaylistBandwidth: comparePlaylistBandwidth,
  50407. comparePlaylistResolution: comparePlaylistResolution,
  50408. xhr: xhrFactory()
  50409. }; // Define getter/setters for config properites
  50410. ['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) {
  50411. Object.defineProperty(Hls$1, prop, {
  50412. get: function get$$1() {
  50413. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  50414. return Config[prop];
  50415. },
  50416. set: function set$$1(value) {
  50417. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  50418. if (typeof value !== 'number' || value < 0) {
  50419. videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
  50420. return;
  50421. }
  50422. Config[prop] = value;
  50423. }
  50424. });
  50425. });
  50426. var LOCAL_STORAGE_KEY$1 = 'videojs-vhs';
  50427. var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
  50428. var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
  50429. if (mpegurlRE.test(type)) {
  50430. return 'hls';
  50431. }
  50432. var dashRE = /^application\/dash\+xml/i;
  50433. if (dashRE.test(type)) {
  50434. return 'dash';
  50435. }
  50436. return null;
  50437. };
  50438. /**
  50439. * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
  50440. *
  50441. * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
  50442. * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
  50443. * @function handleHlsMediaChange
  50444. */
  50445. var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
  50446. var newPlaylist = playlistLoader.media();
  50447. var selectedIndex = -1;
  50448. for (var i = 0; i < qualityLevels.length; i++) {
  50449. if (qualityLevels[i].id === newPlaylist.uri) {
  50450. selectedIndex = i;
  50451. break;
  50452. }
  50453. }
  50454. qualityLevels.selectedIndex_ = selectedIndex;
  50455. qualityLevels.trigger({
  50456. selectedIndex: selectedIndex,
  50457. type: 'change'
  50458. });
  50459. };
  50460. /**
  50461. * Adds quality levels to list once playlist metadata is available
  50462. *
  50463. * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
  50464. * @param {Object} hls Hls object to listen to for media events.
  50465. * @function handleHlsLoadedMetadata
  50466. */
  50467. var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
  50468. hls.representations().forEach(function (rep) {
  50469. qualityLevels.addQualityLevel(rep);
  50470. });
  50471. handleHlsMediaChange(qualityLevels, hls.playlists);
  50472. }; // HLS is a source handler, not a tech. Make sure attempts to use it
  50473. // as one do not cause exceptions.
  50474. Hls$1.canPlaySource = function () {
  50475. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  50476. };
  50477. var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
  50478. if (!keySystemOptions) {
  50479. return keySystemOptions;
  50480. } // upsert the content types based on the selected playlist
  50481. var keySystemContentTypes = {};
  50482. for (var keySystem in keySystemOptions) {
  50483. keySystemContentTypes[keySystem] = {
  50484. audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"',
  50485. videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"'
  50486. };
  50487. if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
  50488. keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
  50489. } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
  50490. // so we need to prevent overwriting the URL entirely
  50491. if (typeof keySystemOptions[keySystem] === 'string') {
  50492. keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
  50493. }
  50494. }
  50495. return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
  50496. };
  50497. var setupEmeOptions = function setupEmeOptions(hlsHandler) {
  50498. if (hlsHandler.options_.sourceType !== 'dash') {
  50499. return;
  50500. }
  50501. var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
  50502. if (player.eme) {
  50503. var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media());
  50504. if (sourceOptions) {
  50505. player.currentSource().keySystems = sourceOptions; // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449
  50506. if (player.eme.initializeMediaKeys) {
  50507. player.eme.initializeMediaKeys();
  50508. }
  50509. }
  50510. }
  50511. };
  50512. var getVhsLocalStorage = function getVhsLocalStorage() {
  50513. if (!window.localStorage) {
  50514. return null;
  50515. }
  50516. var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1);
  50517. if (!storedObject) {
  50518. return null;
  50519. }
  50520. try {
  50521. return JSON.parse(storedObject);
  50522. } catch (e) {
  50523. // someone may have tampered with the value
  50524. return null;
  50525. }
  50526. };
  50527. var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
  50528. if (!window.localStorage) {
  50529. return false;
  50530. }
  50531. var objectToStore = getVhsLocalStorage();
  50532. objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options;
  50533. try {
  50534. window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore));
  50535. } catch (e) {
  50536. // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
  50537. // storage is set to 0).
  50538. // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
  50539. // No need to perform any operation.
  50540. return false;
  50541. }
  50542. return objectToStore;
  50543. };
  50544. /**
  50545. * Whether the browser has built-in HLS support.
  50546. */
  50547. Hls$1.supportsNativeHls = function () {
  50548. var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
  50549. if (!videojs$1.getTech('Html5').isSupported()) {
  50550. return false;
  50551. } // HLS manifests can go by many mime-types
  50552. var canPlay = [// Apple santioned
  50553. 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
  50554. 'audio/mpegurl', // Very common
  50555. 'audio/x-mpegurl', // Very common
  50556. 'application/x-mpegurl', // Included for completeness
  50557. 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
  50558. return canPlay.some(function (canItPlay) {
  50559. return /maybe|probably/i.test(video.canPlayType(canItPlay));
  50560. });
  50561. }();
  50562. Hls$1.supportsNativeDash = function () {
  50563. if (!videojs$1.getTech('Html5').isSupported()) {
  50564. return false;
  50565. }
  50566. return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
  50567. }();
  50568. Hls$1.supportsTypeNatively = function (type) {
  50569. if (type === 'hls') {
  50570. return Hls$1.supportsNativeHls;
  50571. }
  50572. if (type === 'dash') {
  50573. return Hls$1.supportsNativeDash;
  50574. }
  50575. return false;
  50576. };
  50577. /**
  50578. * HLS is a source handler, not a tech. Make sure attempts to use it
  50579. * as one do not cause exceptions.
  50580. */
  50581. Hls$1.isSupported = function () {
  50582. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  50583. };
  50584. var Component$1 = videojs$1.getComponent('Component');
  50585. /**
  50586. * The Hls Handler object, where we orchestrate all of the parts
  50587. * of HLS to interact with video.js
  50588. *
  50589. * @class HlsHandler
  50590. * @extends videojs.Component
  50591. * @param {Object} source the soruce object
  50592. * @param {Tech} tech the parent tech object
  50593. * @param {Object} options optional and required options
  50594. */
  50595. var HlsHandler = function (_Component) {
  50596. inherits$1(HlsHandler, _Component);
  50597. function HlsHandler(source, tech, options) {
  50598. classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for
  50599. // backwards-compatibility
  50600. var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
  50601. if (tech.options_ && tech.options_.playerId) {
  50602. var _player = videojs$1(tech.options_.playerId);
  50603. if (!_player.hasOwnProperty('hls')) {
  50604. Object.defineProperty(_player, 'hls', {
  50605. get: function get$$1() {
  50606. videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.');
  50607. tech.trigger({
  50608. type: 'usage',
  50609. name: 'hls-player-access'
  50610. });
  50611. return _this;
  50612. },
  50613. configurable: true
  50614. });
  50615. } // Set up a reference to the HlsHandler from player.vhs. This allows users to start
  50616. // migrating from player.tech_.hls... to player.vhs... for API access. Although this
  50617. // isn't the most appropriate form of reference for video.js (since all APIs should
  50618. // be provided through core video.js), it is a common pattern for plugins, and vhs
  50619. // will act accordingly.
  50620. _player.vhs = _this; // deprecated, for backwards compatibility
  50621. _player.dash = _this;
  50622. _this.player_ = _player;
  50623. }
  50624. _this.tech_ = tech;
  50625. _this.source_ = source;
  50626. _this.stats = {};
  50627. _this.setOptions_();
  50628. if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
  50629. tech.overrideNativeAudioTracks(true);
  50630. tech.overrideNativeVideoTracks(true);
  50631. } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
  50632. // overriding native HLS only works if audio tracks have been emulated
  50633. // error early if we're misconfigured
  50634. throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
  50635. } // listen for fullscreenchange events for this player so that we
  50636. // can adjust our quality selection quickly
  50637. _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
  50638. var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
  50639. if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
  50640. _this.masterPlaylistController_.smoothQualityChange_();
  50641. }
  50642. }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech
  50643. _this.on(_this.tech_, 'seeking', function () {
  50644. if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) {
  50645. this.setCurrentTime(0);
  50646. }
  50647. });
  50648. _this.on(_this.tech_, 'error', function () {
  50649. if (this.masterPlaylistController_) {
  50650. this.masterPlaylistController_.pauseLoading();
  50651. }
  50652. });
  50653. _this.on(_this.tech_, 'play', _this.play);
  50654. return _this;
  50655. }
  50656. createClass$1(HlsHandler, [{
  50657. key: 'setOptions_',
  50658. value: function setOptions_() {
  50659. var _this2 = this; // defaults
  50660. this.options_.withCredentials = this.options_.withCredentials || false;
  50661. this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false;
  50662. this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
  50663. this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
  50664. this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
  50665. this.options_.customTagParsers = this.options_.customTagParsers || [];
  50666. this.options_.customTagMappers = this.options_.customTagMappers || [];
  50667. this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
  50668. if (typeof this.options_.blacklistDuration !== 'number') {
  50669. this.options_.blacklistDuration = 5 * 60;
  50670. }
  50671. if (typeof this.options_.bandwidth !== 'number') {
  50672. if (this.options_.useBandwidthFromLocalStorage) {
  50673. var storedObject = getVhsLocalStorage();
  50674. if (storedObject && storedObject.bandwidth) {
  50675. this.options_.bandwidth = storedObject.bandwidth;
  50676. this.tech_.trigger({
  50677. type: 'usage',
  50678. name: 'hls-bandwidth-from-local-storage'
  50679. });
  50680. }
  50681. if (storedObject && storedObject.throughput) {
  50682. this.options_.throughput = storedObject.throughput;
  50683. this.tech_.trigger({
  50684. type: 'usage',
  50685. name: 'hls-throughput-from-local-storage'
  50686. });
  50687. }
  50688. }
  50689. } // if bandwidth was not set by options or pulled from local storage, start playlist
  50690. // selection at a reasonable bandwidth
  50691. if (typeof this.options_.bandwidth !== 'number') {
  50692. this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
  50693. } // If the bandwidth number is unchanged from the initial setting
  50694. // then this takes precedence over the enableLowInitialPlaylist option
  50695. this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
  50696. ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys'].forEach(function (option) {
  50697. if (typeof _this2.source_[option] !== 'undefined') {
  50698. _this2.options_[option] = _this2.source_[option];
  50699. }
  50700. });
  50701. this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
  50702. }
  50703. /**
  50704. * called when player.src gets called, handle a new source
  50705. *
  50706. * @param {Object} src the source object to handle
  50707. */
  50708. }, {
  50709. key: 'src',
  50710. value: function src(_src, type) {
  50711. var _this3 = this; // do nothing if the src is falsey
  50712. if (!_src) {
  50713. return;
  50714. }
  50715. this.setOptions_(); // add master playlist controller options
  50716. this.options_.url = this.source_.src;
  50717. this.options_.tech = this.tech_;
  50718. this.options_.externHls = Hls$1;
  50719. this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own
  50720. // setCurrentTime function. This is needed because "seeking" events aren't always
  50721. // reliable. External seeks (via the player object) are handled via middleware.
  50722. this.options_.seekTo = function (time) {
  50723. _this3.tech_.setCurrentTime(time);
  50724. _this3.setCurrentTime(time);
  50725. };
  50726. this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
  50727. this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
  50728. seekable: function seekable$$1() {
  50729. return _this3.seekable();
  50730. },
  50731. media: function media() {
  50732. return _this3.masterPlaylistController_.media();
  50733. }
  50734. }));
  50735. this.masterPlaylistController_.on('error', function () {
  50736. var player = videojs$1.players[_this3.tech_.options_.playerId];
  50737. player.error(_this3.masterPlaylistController_.error);
  50738. }); // `this` in selectPlaylist should be the HlsHandler for backwards
  50739. // compatibility with < v2
  50740. this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
  50741. this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
  50742. this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
  50743. this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
  50744. // controller. Using a custom property for backwards compatibility
  50745. // with < v2
  50746. Object.defineProperties(this, {
  50747. selectPlaylist: {
  50748. get: function get$$1() {
  50749. return this.masterPlaylistController_.selectPlaylist;
  50750. },
  50751. set: function set$$1(selectPlaylist) {
  50752. this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
  50753. }
  50754. },
  50755. throughput: {
  50756. get: function get$$1() {
  50757. return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
  50758. },
  50759. set: function set$$1(throughput) {
  50760. this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
  50761. // for the cumulative average
  50762. this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
  50763. }
  50764. },
  50765. bandwidth: {
  50766. get: function get$$1() {
  50767. return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
  50768. },
  50769. set: function set$$1(bandwidth) {
  50770. this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
  50771. // `count` is set to zero that current value of `rate` isn't included
  50772. // in the cumulative average
  50773. this.masterPlaylistController_.mainSegmentLoader_.throughput = {
  50774. rate: 0,
  50775. count: 0
  50776. };
  50777. }
  50778. },
  50779. /**
  50780. * `systemBandwidth` is a combination of two serial processes bit-rates. The first
  50781. * is the network bitrate provided by `bandwidth` and the second is the bitrate of
  50782. * the entire process after that - decryption, transmuxing, and appending - provided
  50783. * by `throughput`.
  50784. *
  50785. * Since the two process are serial, the overall system bandwidth is given by:
  50786. * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
  50787. */
  50788. systemBandwidth: {
  50789. get: function get$$1() {
  50790. var invBandwidth = 1 / (this.bandwidth || 1);
  50791. var invThroughput = void 0;
  50792. if (this.throughput > 0) {
  50793. invThroughput = 1 / this.throughput;
  50794. } else {
  50795. invThroughput = 0;
  50796. }
  50797. var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
  50798. return systemBitrate;
  50799. },
  50800. set: function set$$1() {
  50801. videojs$1.log.error('The "systemBandwidth" property is read-only');
  50802. }
  50803. }
  50804. });
  50805. if (this.options_.bandwidth) {
  50806. this.bandwidth = this.options_.bandwidth;
  50807. }
  50808. if (this.options_.throughput) {
  50809. this.throughput = this.options_.throughput;
  50810. }
  50811. Object.defineProperties(this.stats, {
  50812. bandwidth: {
  50813. get: function get$$1() {
  50814. return _this3.bandwidth || 0;
  50815. },
  50816. enumerable: true
  50817. },
  50818. mediaRequests: {
  50819. get: function get$$1() {
  50820. return _this3.masterPlaylistController_.mediaRequests_() || 0;
  50821. },
  50822. enumerable: true
  50823. },
  50824. mediaRequestsAborted: {
  50825. get: function get$$1() {
  50826. return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
  50827. },
  50828. enumerable: true
  50829. },
  50830. mediaRequestsTimedout: {
  50831. get: function get$$1() {
  50832. return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
  50833. },
  50834. enumerable: true
  50835. },
  50836. mediaRequestsErrored: {
  50837. get: function get$$1() {
  50838. return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
  50839. },
  50840. enumerable: true
  50841. },
  50842. mediaTransferDuration: {
  50843. get: function get$$1() {
  50844. return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
  50845. },
  50846. enumerable: true
  50847. },
  50848. mediaBytesTransferred: {
  50849. get: function get$$1() {
  50850. return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
  50851. },
  50852. enumerable: true
  50853. },
  50854. mediaSecondsLoaded: {
  50855. get: function get$$1() {
  50856. return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
  50857. },
  50858. enumerable: true
  50859. },
  50860. buffered: {
  50861. get: function get$$1() {
  50862. return timeRangesToArray(_this3.tech_.buffered());
  50863. },
  50864. enumerable: true
  50865. },
  50866. currentTime: {
  50867. get: function get$$1() {
  50868. return _this3.tech_.currentTime();
  50869. },
  50870. enumerable: true
  50871. },
  50872. currentSource: {
  50873. get: function get$$1() {
  50874. return _this3.tech_.currentSource_;
  50875. },
  50876. enumerable: true
  50877. },
  50878. currentTech: {
  50879. get: function get$$1() {
  50880. return _this3.tech_.name_;
  50881. },
  50882. enumerable: true
  50883. },
  50884. duration: {
  50885. get: function get$$1() {
  50886. return _this3.tech_.duration();
  50887. },
  50888. enumerable: true
  50889. },
  50890. master: {
  50891. get: function get$$1() {
  50892. return _this3.playlists.master;
  50893. },
  50894. enumerable: true
  50895. },
  50896. playerDimensions: {
  50897. get: function get$$1() {
  50898. return _this3.tech_.currentDimensions();
  50899. },
  50900. enumerable: true
  50901. },
  50902. seekable: {
  50903. get: function get$$1() {
  50904. return timeRangesToArray(_this3.tech_.seekable());
  50905. },
  50906. enumerable: true
  50907. },
  50908. timestamp: {
  50909. get: function get$$1() {
  50910. return Date.now();
  50911. },
  50912. enumerable: true
  50913. },
  50914. videoPlaybackQuality: {
  50915. get: function get$$1() {
  50916. return _this3.tech_.getVideoPlaybackQuality();
  50917. },
  50918. enumerable: true
  50919. }
  50920. });
  50921. this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
  50922. this.tech_.on('bandwidthupdate', function () {
  50923. if (_this3.options_.useBandwidthFromLocalStorage) {
  50924. updateVhsLocalStorage({
  50925. bandwidth: _this3.bandwidth,
  50926. throughput: Math.round(_this3.throughput)
  50927. });
  50928. }
  50929. });
  50930. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  50931. // Add the manual rendition mix-in to HlsHandler
  50932. renditionSelectionMixin(_this3);
  50933. setupEmeOptions(_this3);
  50934. }); // the bandwidth of the primary segment loader is our best
  50935. // estimate of overall bandwidth
  50936. this.on(this.masterPlaylistController_, 'progress', function () {
  50937. this.tech_.trigger('progress');
  50938. });
  50939. this.tech_.ready(function () {
  50940. return _this3.setupQualityLevels_();
  50941. }); // do nothing if the tech has been disposed already
  50942. // this can occur if someone sets the src in player.ready(), for instance
  50943. if (!this.tech_.el()) {
  50944. return;
  50945. }
  50946. this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
  50947. }
  50948. /**
  50949. * Initializes the quality levels and sets listeners to update them.
  50950. *
  50951. * @method setupQualityLevels_
  50952. * @private
  50953. */
  50954. }, {
  50955. key: 'setupQualityLevels_',
  50956. value: function setupQualityLevels_() {
  50957. var _this4 = this;
  50958. var player = videojs$1.players[this.tech_.options_.playerId];
  50959. if (player && player.qualityLevels) {
  50960. this.qualityLevels_ = player.qualityLevels();
  50961. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  50962. handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
  50963. });
  50964. this.playlists.on('mediachange', function () {
  50965. handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
  50966. });
  50967. }
  50968. }
  50969. /**
  50970. * Begin playing the video.
  50971. */
  50972. }, {
  50973. key: 'play',
  50974. value: function play() {
  50975. this.masterPlaylistController_.play();
  50976. }
  50977. /**
  50978. * a wrapper around the function in MasterPlaylistController
  50979. */
  50980. }, {
  50981. key: 'setCurrentTime',
  50982. value: function setCurrentTime(currentTime) {
  50983. this.masterPlaylistController_.setCurrentTime(currentTime);
  50984. }
  50985. /**
  50986. * a wrapper around the function in MasterPlaylistController
  50987. */
  50988. }, {
  50989. key: 'duration',
  50990. value: function duration$$1() {
  50991. return this.masterPlaylistController_.duration();
  50992. }
  50993. /**
  50994. * a wrapper around the function in MasterPlaylistController
  50995. */
  50996. }, {
  50997. key: 'seekable',
  50998. value: function seekable$$1() {
  50999. return this.masterPlaylistController_.seekable();
  51000. }
  51001. /**
  51002. * Abort all outstanding work and cleanup.
  51003. */
  51004. }, {
  51005. key: 'dispose',
  51006. value: function dispose() {
  51007. if (this.playbackWatcher_) {
  51008. this.playbackWatcher_.dispose();
  51009. }
  51010. if (this.masterPlaylistController_) {
  51011. this.masterPlaylistController_.dispose();
  51012. }
  51013. if (this.qualityLevels_) {
  51014. this.qualityLevels_.dispose();
  51015. }
  51016. if (this.player_) {
  51017. delete this.player_.vhs;
  51018. delete this.player_.dash;
  51019. delete this.player_.hls;
  51020. }
  51021. if (this.tech_ && this.tech_.hls) {
  51022. delete this.tech_.hls;
  51023. }
  51024. get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
  51025. }
  51026. }, {
  51027. key: 'convertToProgramTime',
  51028. value: function convertToProgramTime(time, callback) {
  51029. return getProgramTime({
  51030. playlist: this.masterPlaylistController_.media(),
  51031. time: time,
  51032. callback: callback
  51033. });
  51034. } // the player must be playing before calling this
  51035. }, {
  51036. key: 'seekToProgramTime',
  51037. value: function seekToProgramTime$$1(programTime, callback) {
  51038. var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
  51039. var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
  51040. return seekToProgramTime({
  51041. programTime: programTime,
  51042. playlist: this.masterPlaylistController_.media(),
  51043. retryCount: retryCount,
  51044. pauseAfterSeek: pauseAfterSeek,
  51045. seekTo: this.options_.seekTo,
  51046. tech: this.options_.tech,
  51047. callback: callback
  51048. });
  51049. }
  51050. }]);
  51051. return HlsHandler;
  51052. }(Component$1);
  51053. /**
  51054. * The Source Handler object, which informs video.js what additional
  51055. * MIME types are supported and sets up playback. It is registered
  51056. * automatically to the appropriate tech based on the capabilities of
  51057. * the browser it is running in. It is not necessary to use or modify
  51058. * this object in normal usage.
  51059. */
  51060. var HlsSourceHandler = {
  51061. name: 'videojs-http-streaming',
  51062. VERSION: version$1,
  51063. canHandleSource: function canHandleSource(srcObj) {
  51064. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  51065. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  51066. return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
  51067. },
  51068. handleSource: function handleSource(source, tech) {
  51069. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  51070. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  51071. tech.hls = new HlsHandler(source, tech, localOptions);
  51072. tech.hls.xhr = xhrFactory();
  51073. tech.hls.src(source.src, source.type);
  51074. return tech.hls;
  51075. },
  51076. canPlayType: function canPlayType(type) {
  51077. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  51078. var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
  51079. overrideNative = _videojs$mergeOptions.hls.overrideNative;
  51080. var supportedType = simpleTypeFromSourceType(type);
  51081. var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
  51082. return canUseMsePlayback ? 'maybe' : '';
  51083. }
  51084. };
  51085. if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
  51086. videojs$1.MediaSource = MediaSource;
  51087. videojs$1.URL = URL$1;
  51088. } // register source handlers with the appropriate techs
  51089. if (MediaSource.supportsNativeMediaSources()) {
  51090. videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
  51091. }
  51092. videojs$1.HlsHandler = HlsHandler;
  51093. videojs$1.HlsSourceHandler = HlsSourceHandler;
  51094. videojs$1.Hls = Hls$1;
  51095. if (!videojs$1.use) {
  51096. videojs$1.registerComponent('Hls', Hls$1);
  51097. }
  51098. videojs$1.options.hls = videojs$1.options.hls || {};
  51099. if (videojs$1.registerPlugin) {
  51100. videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
  51101. } else {
  51102. videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
  51103. }
  51104. return videojs$1;
  51105. }));
  51106. !function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.6.0")}(window)}();