暂无描述

video.cjs.js 1.3MB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810328104281052810628107281082810928110281112811228113281142811528116281172811828119281202812128122281232812428125281262812728128281292813028131281322813328134281352813628137281382813928140281412814228143281442814528146281472814828149281502815128152281532815428155281562815728158281592816028161281622816328164281652816628167281682816928170281712817228173281742817528176281772817828179281802818128182281832818428185281862818728188281892819028191281922819328194281952819628197281982819928200282012820228203282042820528206282072820828209282102821128212282132821428215282162821728218282192822028221282222822328224282252822628227282282822928230282312823228233282342823528236282372823828239282402824128242282432824428245282462824728248282492825028251282522825328254282552825628257282582825928260282612826228263282642826528266282672826828269282702827128272282732827428275282762827728278282792828028281282822828328284282852828628287282882828928290282912829228293282942829528296282972829828299283002830128302283032830428305283062830728308283092831028311283122831328314283152831628317283182831928320283212832228323283242832528326283272832828329283302833128332283332833428335283362833728338283392834028341283422834328344283452834628347283482834928350283512835228353283542835528356283572835828359283602836128362283632836428365283662836728368283692837028371283722837328374283752837628377283782837928380283812838228383283842838528386283872838828389283902839128392283932839428395283962839728398283992840028401284022840328404284052840628407284082840928410284112841228413284142841528416284172841828419284202842128422284232842428425284262842728428284292843028431284322843328434284352843628437284382843928440284412844228443284442844528446284472844828449284502845128452284532845428455284562845728458284592846028461284622846328464284652846628467284682846928470284712847228473284742847528476284772847828479284802848128482284832848428485284862848728488284892849028491284922849328494284952849628497284982849928500285012850228503285042850528506285072850828509285102851128512285132851428515285162851728518285192852028521285222852328524285252852628527285282852928530285312853228533285342853528536285372853828539285402854128542285432854428545285462854728548285492855028551285522855328554285552855628557285582855928560285612856228563285642856528566285672856828569285702857128572285732857428575285762857728578285792858028581285822858328584285852858628587285882858928590285912859228593285942859528596285972859828599286002860128602286032860428605286062860728608286092861028611286122861328614286152861628617286182861928620286212862228623286242862528626286272862828629286302863128632286332863428635286362863728638286392864028641286422864328644286452864628647286482864928650286512865228653286542865528656286572865828659286602866128662286632866428665286662866728668286692867028671286722867328674286752867628677286782867928680286812868228683286842868528686286872868828689286902869128692286932869428695286962869728698286992870028701287022870328704287052870628707287082870928710287112871228713287142871528716287172871828719287202872128722287232872428725287262872728728287292873028731287322873328734287352873628737287382873928740287412874228743287442874528746287472874828749287502875128752287532875428755287562875728758287592876028761287622876328764287652876628767287682876928770287712877228773287742877528776287772877828779287802878128782287832878428785287862878728788287892879028791287922879328794287952879628797287982879928800288012880228803288042880528806288072880828809288102881128812288132881428815288162881728818288192882028821288222882328824288252882628827288282882928830288312883228833288342883528836288372883828839288402884128842288432884428845288462884728848288492885028851288522885328854288552885628857288582885928860288612886228863288642886528866288672886828869288702887128872288732887428875288762887728878288792888028881288822888328884288852888628887288882888928890288912889228893288942889528896288972889828899289002890128902289032890428905289062890728908289092891028911289122891328914289152891628917289182891928920289212892228923289242892528926289272892828929289302893128932289332893428935289362893728938289392894028941289422894328944289452894628947289482894928950289512895228953289542895528956289572895828959289602896128962289632896428965289662896728968289692897028971289722897328974289752897628977289782897928980289812898228983289842898528986289872898828989289902899128992289932899428995289962899728998289992900029001290022900329004290052900629007290082900929010290112901229013290142901529016290172901829019290202902129022290232902429025290262902729028290292903029031290322903329034290352903629037290382903929040290412904229043290442904529046290472904829049290502905129052290532905429055290562905729058290592906029061290622906329064290652906629067290682906929070290712907229073290742907529076290772907829079290802908129082290832908429085290862908729088290892909029091290922909329094290952909629097290982909929100291012910229103291042910529106291072910829109291102911129112291132911429115291162911729118291192912029121291222912329124291252912629127291282912929130291312913229133291342913529136291372913829139291402914129142291432914429145291462914729148291492915029151291522915329154291552915629157291582915929160291612916229163291642916529166291672916829169291702917129172291732917429175291762917729178291792918029181291822918329184291852918629187291882918929190291912919229193291942919529196291972919829199292002920129202292032920429205292062920729208292092921029211292122921329214292152921629217292182921929220292212922229223292242922529226292272922829229292302923129232292332923429235292362923729238292392924029241292422924329244292452924629247292482924929250292512925229253292542925529256292572925829259292602926129262292632926429265292662926729268292692927029271292722927329274292752927629277292782927929280292812928229283292842928529286292872928829289292902929129292292932929429295292962929729298292992930029301293022930329304293052930629307293082930929310293112931229313293142931529316293172931829319293202932129322293232932429325293262932729328293292933029331293322933329334293352933629337293382933929340293412934229343293442934529346293472934829349293502935129352293532935429355293562935729358293592936029361293622936329364293652936629367293682936929370293712937229373293742937529376293772937829379293802938129382293832938429385293862938729388293892939029391293922939329394293952939629397293982939929400294012940229403294042940529406294072940829409294102941129412294132941429415294162941729418294192942029421294222942329424294252942629427294282942929430294312943229433294342943529436294372943829439294402944129442294432944429445294462944729448294492945029451294522945329454294552945629457294582945929460294612946229463294642946529466294672946829469294702947129472294732947429475294762947729478294792948029481294822948329484294852948629487294882948929490294912949229493294942949529496294972949829499295002950129502295032950429505295062950729508295092951029511295122951329514295152951629517295182951929520295212952229523295242952529526295272952829529295302953129532295332953429535295362953729538295392954029541295422954329544295452954629547295482954929550295512955229553295542955529556295572955829559295602956129562295632956429565295662956729568295692957029571295722957329574295752957629577295782957929580295812958229583295842958529586295872958829589295902959129592295932959429595295962959729598295992960029601296022960329604296052960629607296082960929610296112961229613296142961529616296172961829619296202962129622296232962429625296262962729628296292963029631296322963329634296352963629637296382963929640296412964229643296442964529646296472964829649296502965129652296532965429655296562965729658296592966029661296622966329664296652966629667296682966929670296712967229673296742967529676296772967829679296802968129682296832968429685296862968729688296892969029691296922969329694296952969629697296982969929700297012970229703297042970529706297072970829709297102971129712297132971429715297162971729718297192972029721297222972329724297252972629727297282972929730297312973229733297342973529736297372973829739297402974129742297432974429745297462974729748297492975029751297522975329754297552975629757297582975929760297612976229763297642976529766297672976829769297702977129772297732977429775297762977729778297792978029781297822978329784297852978629787297882978929790297912979229793297942979529796297972979829799298002980129802298032980429805298062980729808298092981029811298122981329814298152981629817298182981929820298212982229823298242982529826298272982829829298302983129832298332983429835298362983729838298392984029841298422984329844298452984629847298482984929850298512985229853298542985529856298572985829859298602986129862298632986429865298662986729868298692987029871298722987329874298752987629877298782987929880298812988229883298842988529886298872988829889298902989129892298932989429895298962989729898298992990029901299022990329904299052990629907299082990929910299112991229913299142991529916299172991829919299202992129922299232992429925299262992729928299292993029931299322993329934299352993629937299382993929940299412994229943299442994529946299472994829949299502995129952299532995429955299562995729958299592996029961299622996329964299652996629967299682996929970299712997229973299742997529976299772997829979299802998129982299832998429985299862998729988299892999029991299922999329994299952999629997299982999930000300013000230003300043000530006300073000830009300103001130012300133001430015300163001730018300193002030021300223002330024300253002630027300283002930030300313003230033300343003530036300373003830039300403004130042300433004430045300463004730048300493005030051300523005330054300553005630057300583005930060300613006230063300643006530066300673006830069300703007130072300733007430075300763007730078300793008030081300823008330084300853008630087300883008930090300913009230093300943009530096300973009830099301003010130102301033010430105301063010730108301093011030111301123011330114301153011630117301183011930120301213012230123301243012530126301273012830129301303013130132301333013430135301363013730138301393014030141301423014330144301453014630147301483014930150301513015230153301543015530156301573015830159301603016130162301633016430165301663016730168301693017030171301723017330174301753017630177301783017930180301813018230183301843018530186301873018830189301903019130192301933019430195301963019730198301993020030201302023020330204302053020630207302083020930210302113021230213302143021530216302173021830219302203022130222302233022430225302263022730228302293023030231302323023330234302353023630237302383023930240302413024230243302443024530246302473024830249302503025130252302533025430255302563025730258302593026030261302623026330264302653026630267302683026930270302713027230273302743027530276302773027830279302803028130282302833028430285302863028730288302893029030291302923029330294302953029630297302983029930300303013030230303303043030530306303073030830309303103031130312303133031430315303163031730318303193032030321303223032330324303253032630327303283032930330303313033230333303343033530336303373033830339303403034130342303433034430345303463034730348303493035030351303523035330354303553035630357303583035930360303613036230363303643036530366303673036830369303703037130372303733037430375303763037730378303793038030381303823038330384303853038630387303883038930390303913039230393303943039530396303973039830399304003040130402304033040430405304063040730408304093041030411304123041330414304153041630417304183041930420304213042230423304243042530426304273042830429304303043130432304333043430435304363043730438304393044030441304423044330444304453044630447304483044930450304513045230453304543045530456304573045830459304603046130462304633046430465304663046730468304693047030471304723047330474304753047630477304783047930480304813048230483304843048530486304873048830489304903049130492304933049430495304963049730498304993050030501305023050330504305053050630507305083050930510305113051230513305143051530516305173051830519305203052130522305233052430525305263052730528305293053030531305323053330534305353053630537305383053930540305413054230543305443054530546305473054830549305503055130552305533055430555305563055730558305593056030561305623056330564305653056630567305683056930570305713057230573305743057530576305773057830579305803058130582305833058430585305863058730588305893059030591305923059330594305953059630597305983059930600306013060230603306043060530606306073060830609306103061130612306133061430615306163061730618306193062030621306223062330624306253062630627306283062930630306313063230633306343063530636306373063830639306403064130642306433064430645306463064730648306493065030651306523065330654306553065630657306583065930660306613066230663306643066530666306673066830669306703067130672306733067430675306763067730678306793068030681306823068330684306853068630687306883068930690306913069230693306943069530696306973069830699307003070130702307033070430705307063070730708307093071030711307123071330714307153071630717307183071930720307213072230723307243072530726307273072830729307303073130732307333073430735307363073730738307393074030741307423074330744307453074630747307483074930750307513075230753307543075530756307573075830759307603076130762307633076430765307663076730768307693077030771307723077330774307753077630777307783077930780307813078230783307843078530786307873078830789307903079130792307933079430795307963079730798307993080030801308023080330804308053080630807308083080930810308113081230813308143081530816308173081830819308203082130822308233082430825308263082730828308293083030831308323083330834308353083630837308383083930840308413084230843308443084530846308473084830849308503085130852308533085430855308563085730858308593086030861308623086330864308653086630867308683086930870308713087230873308743087530876308773087830879308803088130882308833088430885308863088730888308893089030891308923089330894308953089630897308983089930900309013090230903309043090530906309073090830909309103091130912309133091430915309163091730918309193092030921309223092330924309253092630927309283092930930309313093230933309343093530936309373093830939309403094130942309433094430945309463094730948309493095030951309523095330954309553095630957309583095930960309613096230963309643096530966309673096830969309703097130972309733097430975309763097730978309793098030981309823098330984309853098630987309883098930990309913099230993309943099530996309973099830999310003100131002310033100431005310063100731008310093101031011310123101331014310153101631017310183101931020310213102231023310243102531026310273102831029310303103131032310333103431035310363103731038310393104031041310423104331044310453104631047310483104931050310513105231053310543105531056310573105831059310603106131062310633106431065310663106731068310693107031071310723107331074310753107631077310783107931080310813108231083310843108531086310873108831089310903109131092310933109431095310963109731098310993110031101311023110331104311053110631107311083110931110311113111231113311143111531116311173111831119311203112131122311233112431125311263112731128311293113031131311323113331134311353113631137311383113931140311413114231143311443114531146311473114831149311503115131152311533115431155311563115731158311593116031161311623116331164311653116631167311683116931170311713117231173311743117531176311773117831179311803118131182311833118431185311863118731188311893119031191311923119331194311953119631197311983119931200312013120231203312043120531206312073120831209312103121131212312133121431215312163121731218312193122031221312223122331224312253122631227312283122931230312313123231233312343123531236312373123831239312403124131242312433124431245312463124731248312493125031251312523125331254312553125631257312583125931260312613126231263312643126531266312673126831269312703127131272312733127431275312763127731278312793128031281312823128331284312853128631287312883128931290312913129231293312943129531296312973129831299313003130131302313033130431305313063130731308313093131031311313123131331314313153131631317313183131931320313213132231323313243132531326313273132831329313303133131332313333133431335313363133731338313393134031341313423134331344313453134631347313483134931350313513135231353313543135531356313573135831359313603136131362313633136431365313663136731368313693137031371313723137331374313753137631377313783137931380313813138231383313843138531386313873138831389313903139131392313933139431395313963139731398313993140031401314023140331404314053140631407314083140931410314113141231413314143141531416314173141831419314203142131422314233142431425314263142731428314293143031431314323143331434314353143631437314383143931440314413144231443314443144531446314473144831449314503145131452314533145431455314563145731458314593146031461314623146331464314653146631467314683146931470314713147231473314743147531476314773147831479314803148131482314833148431485314863148731488314893149031491314923149331494314953149631497314983149931500315013150231503315043150531506315073150831509315103151131512315133151431515315163151731518315193152031521315223152331524315253152631527315283152931530315313153231533315343153531536315373153831539315403154131542315433154431545315463154731548315493155031551315523155331554315553155631557315583155931560315613156231563315643156531566315673156831569315703157131572315733157431575315763157731578315793158031581315823158331584315853158631587315883158931590315913159231593315943159531596315973159831599316003160131602316033160431605316063160731608316093161031611316123161331614316153161631617316183161931620316213162231623316243162531626316273162831629316303163131632316333163431635316363163731638316393164031641316423164331644316453164631647316483164931650316513165231653316543165531656316573165831659316603166131662316633166431665316663166731668316693167031671316723167331674316753167631677316783167931680316813168231683316843168531686316873168831689316903169131692316933169431695316963169731698316993170031701317023170331704317053170631707317083170931710317113171231713317143171531716317173171831719317203172131722317233172431725317263172731728317293173031731317323173331734317353173631737317383173931740317413174231743317443174531746317473174831749317503175131752317533175431755317563175731758317593176031761317623176331764317653176631767317683176931770317713177231773317743177531776317773177831779317803178131782317833178431785317863178731788317893179031791317923179331794317953179631797317983179931800318013180231803318043180531806318073180831809318103181131812318133181431815318163181731818318193182031821318223182331824318253182631827318283182931830318313183231833318343183531836318373183831839318403184131842318433184431845318463184731848318493185031851318523185331854318553185631857318583185931860318613186231863318643186531866318673186831869318703187131872318733187431875318763187731878318793188031881318823188331884318853188631887318883188931890318913189231893318943189531896318973189831899319003190131902319033190431905319063190731908319093191031911319123191331914319153191631917319183191931920319213192231923319243192531926319273192831929319303193131932319333193431935319363193731938319393194031941319423194331944319453194631947319483194931950319513195231953319543195531956319573195831959319603196131962319633196431965319663196731968319693197031971319723197331974319753197631977319783197931980319813198231983319843198531986319873198831989319903199131992319933199431995319963199731998319993200032001320023200332004320053200632007320083200932010320113201232013320143201532016320173201832019320203202132022320233202432025320263202732028320293203032031320323203332034320353203632037320383203932040320413204232043320443204532046320473204832049320503205132052320533205432055320563205732058320593206032061320623206332064320653206632067320683206932070320713207232073320743207532076320773207832079320803208132082320833208432085320863208732088320893209032091320923209332094320953209632097320983209932100321013210232103321043210532106321073210832109321103211132112321133211432115321163211732118321193212032121321223212332124321253212632127321283212932130321313213232133321343213532136321373213832139321403214132142321433214432145321463214732148321493215032151321523215332154321553215632157321583215932160321613216232163321643216532166321673216832169321703217132172321733217432175321763217732178321793218032181321823218332184321853218632187321883218932190321913219232193321943219532196321973219832199322003220132202322033220432205322063220732208322093221032211322123221332214322153221632217322183221932220322213222232223322243222532226322273222832229322303223132232322333223432235322363223732238322393224032241322423224332244322453224632247322483224932250322513225232253322543225532256322573225832259322603226132262322633226432265322663226732268322693227032271322723227332274322753227632277322783227932280322813228232283322843228532286322873228832289322903229132292322933229432295322963229732298322993230032301323023230332304323053230632307323083230932310323113231232313323143231532316323173231832319323203232132322323233232432325323263232732328323293233032331323323233332334323353233632337323383233932340323413234232343323443234532346323473234832349323503235132352323533235432355323563235732358323593236032361323623236332364323653236632367323683236932370323713237232373323743237532376323773237832379323803238132382323833238432385323863238732388323893239032391323923239332394323953239632397323983239932400324013240232403324043240532406324073240832409324103241132412324133241432415324163241732418324193242032421324223242332424324253242632427324283242932430324313243232433324343243532436324373243832439324403244132442324433244432445324463244732448324493245032451324523245332454324553245632457324583245932460324613246232463324643246532466324673246832469324703247132472324733247432475324763247732478324793248032481324823248332484324853248632487324883248932490324913249232493324943249532496324973249832499325003250132502325033250432505325063250732508325093251032511325123251332514325153251632517325183251932520325213252232523325243252532526325273252832529325303253132532325333253432535325363253732538325393254032541325423254332544325453254632547325483254932550325513255232553325543255532556325573255832559325603256132562325633256432565325663256732568325693257032571325723257332574325753257632577325783257932580325813258232583325843258532586325873258832589325903259132592325933259432595325963259732598325993260032601326023260332604326053260632607326083260932610326113261232613326143261532616326173261832619326203262132622326233262432625326263262732628326293263032631326323263332634326353263632637326383263932640326413264232643326443264532646326473264832649326503265132652326533265432655326563265732658326593266032661326623266332664326653266632667326683266932670326713267232673326743267532676326773267832679326803268132682326833268432685326863268732688326893269032691326923269332694326953269632697326983269932700327013270232703327043270532706327073270832709327103271132712327133271432715327163271732718327193272032721327223272332724327253272632727327283272932730327313273232733327343273532736327373273832739327403274132742327433274432745327463274732748327493275032751327523275332754327553275632757327583275932760327613276232763327643276532766327673276832769327703277132772327733277432775327763277732778327793278032781327823278332784327853278632787327883278932790327913279232793327943279532796327973279832799328003280132802328033280432805328063280732808328093281032811328123281332814328153281632817328183281932820328213282232823328243282532826328273282832829328303283132832328333283432835328363283732838328393284032841328423284332844328453284632847328483284932850328513285232853328543285532856328573285832859328603286132862328633286432865328663286732868328693287032871328723287332874328753287632877328783287932880328813288232883328843288532886328873288832889328903289132892328933289432895328963289732898328993290032901329023290332904329053290632907329083290932910329113291232913329143291532916329173291832919329203292132922329233292432925329263292732928329293293032931329323293332934329353293632937329383293932940329413294232943329443294532946329473294832949329503295132952329533295432955329563295732958329593296032961329623296332964329653296632967329683296932970329713297232973329743297532976329773297832979329803298132982329833298432985329863298732988329893299032991329923299332994329953299632997329983299933000330013300233003330043300533006330073300833009330103301133012330133301433015330163301733018330193302033021330223302333024330253302633027330283302933030330313303233033330343303533036330373303833039330403304133042330433304433045330463304733048330493305033051330523305333054330553305633057330583305933060330613306233063330643306533066330673306833069330703307133072330733307433075330763307733078330793308033081330823308333084330853308633087330883308933090330913309233093330943309533096330973309833099331003310133102331033310433105331063310733108331093311033111331123311333114331153311633117331183311933120331213312233123331243312533126331273312833129331303313133132331333313433135331363313733138331393314033141331423314333144331453314633147331483314933150331513315233153331543315533156331573315833159331603316133162331633316433165331663316733168331693317033171331723317333174331753317633177331783317933180331813318233183331843318533186331873318833189331903319133192331933319433195331963319733198331993320033201332023320333204332053320633207332083320933210332113321233213332143321533216332173321833219332203322133222332233322433225332263322733228332293323033231332323323333234332353323633237332383323933240332413324233243332443324533246332473324833249332503325133252332533325433255332563325733258332593326033261332623326333264332653326633267332683326933270332713327233273332743327533276332773327833279332803328133282332833328433285332863328733288332893329033291332923329333294332953329633297332983329933300333013330233303333043330533306333073330833309333103331133312333133331433315333163331733318333193332033321333223332333324333253332633327333283332933330333313333233333333343333533336333373333833339333403334133342333433334433345333463334733348333493335033351333523335333354333553335633357333583335933360333613336233363333643336533366333673336833369333703337133372333733337433375333763337733378333793338033381333823338333384333853338633387333883338933390333913339233393333943339533396333973339833399334003340133402334033340433405334063340733408334093341033411334123341333414334153341633417334183341933420334213342233423334243342533426334273342833429334303343133432334333343433435334363343733438334393344033441334423344333444334453344633447334483344933450334513345233453334543345533456334573345833459334603346133462334633346433465334663346733468334693347033471334723347333474334753347633477334783347933480334813348233483334843348533486334873348833489334903349133492334933349433495334963349733498334993350033501335023350333504335053350633507335083350933510335113351233513335143351533516335173351833519335203352133522335233352433525335263352733528335293353033531335323353333534335353353633537335383353933540335413354233543335443354533546335473354833549335503355133552335533355433555335563355733558335593356033561335623356333564335653356633567335683356933570335713357233573335743357533576335773357833579335803358133582335833358433585335863358733588335893359033591335923359333594335953359633597335983359933600336013360233603336043360533606336073360833609336103361133612336133361433615336163361733618336193362033621336223362333624336253362633627336283362933630336313363233633336343363533636336373363833639336403364133642336433364433645336463364733648336493365033651336523365333654336553365633657336583365933660336613366233663336643366533666336673366833669336703367133672336733367433675336763367733678336793368033681336823368333684336853368633687336883368933690336913369233693336943369533696336973369833699337003370133702337033370433705337063370733708337093371033711337123371333714337153371633717337183371933720337213372233723337243372533726337273372833729337303373133732337333373433735337363373733738337393374033741337423374333744337453374633747337483374933750337513375233753337543375533756337573375833759337603376133762337633376433765337663376733768337693377033771337723377333774337753377633777337783377933780337813378233783337843378533786337873378833789337903379133792337933379433795337963379733798337993380033801338023380333804338053380633807338083380933810338113381233813338143381533816338173381833819338203382133822338233382433825338263382733828338293383033831338323383333834338353383633837338383383933840338413384233843338443384533846338473384833849338503385133852338533385433855338563385733858338593386033861338623386333864338653386633867338683386933870338713387233873338743387533876338773387833879338803388133882338833388433885338863388733888338893389033891338923389333894338953389633897338983389933900339013390233903339043390533906339073390833909339103391133912339133391433915339163391733918339193392033921339223392333924339253392633927339283392933930339313393233933339343393533936339373393833939339403394133942339433394433945339463394733948339493395033951339523395333954339553395633957339583395933960339613396233963339643396533966339673396833969339703397133972339733397433975339763397733978339793398033981339823398333984339853398633987339883398933990339913399233993339943399533996339973399833999340003400134002340033400434005340063400734008340093401034011340123401334014340153401634017340183401934020340213402234023340243402534026340273402834029340303403134032340333403434035340363403734038340393404034041340423404334044340453404634047340483404934050340513405234053340543405534056340573405834059340603406134062340633406434065340663406734068340693407034071340723407334074340753407634077340783407934080340813408234083340843408534086340873408834089340903409134092340933409434095340963409734098340993410034101341023410334104341053410634107341083410934110341113411234113341143411534116341173411834119341203412134122341233412434125341263412734128341293413034131341323413334134341353413634137341383413934140341413414234143341443414534146341473414834149341503415134152341533415434155341563415734158341593416034161341623416334164341653416634167341683416934170341713417234173341743417534176341773417834179341803418134182341833418434185341863418734188341893419034191341923419334194341953419634197341983419934200342013420234203342043420534206342073420834209342103421134212342133421434215342163421734218342193422034221342223422334224342253422634227342283422934230342313423234233342343423534236342373423834239342403424134242342433424434245342463424734248342493425034251342523425334254342553425634257342583425934260342613426234263342643426534266342673426834269342703427134272342733427434275342763427734278342793428034281342823428334284342853428634287342883428934290342913429234293342943429534296342973429834299343003430134302343033430434305343063430734308343093431034311343123431334314343153431634317343183431934320343213432234323343243432534326343273432834329343303433134332343333433434335343363433734338343393434034341343423434334344343453434634347343483434934350343513435234353343543435534356343573435834359343603436134362343633436434365343663436734368343693437034371343723437334374343753437634377343783437934380343813438234383343843438534386343873438834389343903439134392343933439434395343963439734398343993440034401344023440334404344053440634407344083440934410344113441234413344143441534416344173441834419344203442134422344233442434425344263442734428344293443034431344323443334434344353443634437344383443934440344413444234443344443444534446344473444834449344503445134452344533445434455344563445734458344593446034461344623446334464344653446634467344683446934470344713447234473344743447534476344773447834479344803448134482344833448434485344863448734488344893449034491344923449334494344953449634497344983449934500345013450234503345043450534506345073450834509345103451134512345133451434515345163451734518345193452034521345223452334524345253452634527345283452934530345313453234533345343453534536345373453834539345403454134542345433454434545345463454734548345493455034551345523455334554345553455634557345583455934560345613456234563345643456534566345673456834569345703457134572345733457434575345763457734578345793458034581345823458334584345853458634587345883458934590345913459234593345943459534596345973459834599346003460134602346033460434605346063460734608346093461034611346123461334614346153461634617346183461934620346213462234623346243462534626346273462834629346303463134632346333463434635346363463734638346393464034641346423464334644346453464634647346483464934650346513465234653346543465534656346573465834659346603466134662346633466434665346663466734668346693467034671346723467334674346753467634677346783467934680346813468234683346843468534686346873468834689346903469134692346933469434695346963469734698346993470034701347023470334704347053470634707347083470934710347113471234713347143471534716347173471834719347203472134722347233472434725347263472734728347293473034731347323473334734347353473634737347383473934740347413474234743347443474534746347473474834749347503475134752347533475434755347563475734758347593476034761347623476334764347653476634767347683476934770347713477234773347743477534776347773477834779347803478134782347833478434785347863478734788347893479034791347923479334794347953479634797347983479934800348013480234803348043480534806348073480834809348103481134812348133481434815348163481734818348193482034821348223482334824348253482634827348283482934830348313483234833348343483534836348373483834839348403484134842348433484434845348463484734848348493485034851348523485334854348553485634857348583485934860348613486234863348643486534866348673486834869348703487134872348733487434875348763487734878348793488034881348823488334884348853488634887348883488934890348913489234893348943489534896348973489834899349003490134902349033490434905349063490734908349093491034911349123491334914349153491634917349183491934920349213492234923349243492534926349273492834929349303493134932349333493434935349363493734938349393494034941349423494334944349453494634947349483494934950349513495234953349543495534956349573495834959349603496134962349633496434965349663496734968349693497034971349723497334974349753497634977349783497934980349813498234983349843498534986349873498834989349903499134992349933499434995349963499734998349993500035001350023500335004350053500635007350083500935010350113501235013350143501535016350173501835019350203502135022350233502435025350263502735028350293503035031350323503335034350353503635037350383503935040350413504235043350443504535046350473504835049350503505135052350533505435055350563505735058350593506035061350623506335064350653506635067350683506935070350713507235073350743507535076350773507835079350803508135082350833508435085350863508735088350893509035091350923509335094350953509635097350983509935100351013510235103351043510535106351073510835109351103511135112351133511435115351163511735118351193512035121351223512335124351253512635127351283512935130351313513235133351343513535136351373513835139351403514135142351433514435145351463514735148351493515035151351523515335154351553515635157351583515935160351613516235163351643516535166351673516835169351703517135172351733517435175351763517735178351793518035181351823518335184351853518635187351883518935190351913519235193351943519535196351973519835199352003520135202352033520435205352063520735208352093521035211352123521335214352153521635217352183521935220352213522235223352243522535226352273522835229352303523135232352333523435235352363523735238352393524035241352423524335244352453524635247352483524935250352513525235253352543525535256352573525835259352603526135262352633526435265352663526735268352693527035271352723527335274352753527635277352783527935280352813528235283352843528535286352873528835289352903529135292352933529435295352963529735298352993530035301353023530335304353053530635307353083530935310353113531235313353143531535316353173531835319353203532135322353233532435325353263532735328353293533035331353323533335334353353533635337353383533935340353413534235343353443534535346353473534835349353503535135352353533535435355353563535735358353593536035361353623536335364353653536635367353683536935370353713537235373353743537535376353773537835379353803538135382353833538435385353863538735388353893539035391353923539335394353953539635397353983539935400354013540235403354043540535406354073540835409354103541135412354133541435415354163541735418354193542035421354223542335424354253542635427354283542935430354313543235433354343543535436354373543835439354403544135442354433544435445354463544735448354493545035451354523545335454354553545635457354583545935460354613546235463354643546535466354673546835469354703547135472354733547435475354763547735478354793548035481354823548335484354853548635487354883548935490354913549235493354943549535496354973549835499355003550135502355033550435505355063550735508355093551035511355123551335514355153551635517355183551935520355213552235523355243552535526355273552835529355303553135532355333553435535355363553735538355393554035541355423554335544355453554635547355483554935550355513555235553355543555535556355573555835559355603556135562355633556435565355663556735568355693557035571355723557335574355753557635577355783557935580355813558235583355843558535586355873558835589355903559135592355933559435595355963559735598355993560035601356023560335604356053560635607356083560935610356113561235613356143561535616356173561835619356203562135622356233562435625356263562735628356293563035631356323563335634356353563635637356383563935640356413564235643356443564535646356473564835649356503565135652356533565435655356563565735658356593566035661356623566335664356653566635667356683566935670356713567235673356743567535676356773567835679356803568135682356833568435685356863568735688356893569035691356923569335694356953569635697356983569935700357013570235703357043570535706357073570835709357103571135712357133571435715357163571735718357193572035721357223572335724357253572635727357283572935730357313573235733357343573535736357373573835739357403574135742357433574435745357463574735748357493575035751357523575335754357553575635757357583575935760357613576235763357643576535766357673576835769357703577135772357733577435775357763577735778357793578035781357823578335784357853578635787357883578935790357913579235793357943579535796357973579835799358003580135802358033580435805358063580735808358093581035811358123581335814358153581635817358183581935820358213582235823358243582535826358273582835829358303583135832358333583435835358363583735838358393584035841358423584335844358453584635847358483584935850358513585235853358543585535856358573585835859358603586135862358633586435865358663586735868358693587035871358723587335874358753587635877358783587935880358813588235883358843588535886358873588835889358903589135892358933589435895358963589735898358993590035901359023590335904359053590635907359083590935910359113591235913359143591535916359173591835919359203592135922359233592435925359263592735928359293593035931359323593335934359353593635937359383593935940359413594235943359443594535946359473594835949359503595135952359533595435955359563595735958359593596035961359623596335964359653596635967359683596935970359713597235973359743597535976359773597835979359803598135982359833598435985359863598735988359893599035991359923599335994359953599635997359983599936000360013600236003360043600536006360073600836009360103601136012360133601436015360163601736018360193602036021360223602336024360253602636027360283602936030360313603236033360343603536036360373603836039360403604136042360433604436045360463604736048360493605036051360523605336054360553605636057360583605936060360613606236063360643606536066360673606836069360703607136072360733607436075360763607736078360793608036081360823608336084360853608636087360883608936090360913609236093360943609536096360973609836099361003610136102361033610436105361063610736108361093611036111361123611336114361153611636117361183611936120361213612236123361243612536126361273612836129361303613136132361333613436135361363613736138361393614036141361423614336144361453614636147361483614936150361513615236153361543615536156361573615836159361603616136162361633616436165361663616736168361693617036171361723617336174361753617636177361783617936180361813618236183361843618536186361873618836189361903619136192361933619436195361963619736198361993620036201362023620336204362053620636207362083620936210362113621236213362143621536216362173621836219362203622136222362233622436225362263622736228362293623036231362323623336234362353623636237362383623936240362413624236243362443624536246362473624836249362503625136252362533625436255362563625736258362593626036261362623626336264362653626636267362683626936270362713627236273362743627536276362773627836279362803628136282362833628436285362863628736288362893629036291362923629336294362953629636297362983629936300363013630236303363043630536306363073630836309363103631136312363133631436315363163631736318363193632036321363223632336324363253632636327363283632936330363313633236333363343633536336363373633836339363403634136342363433634436345363463634736348363493635036351363523635336354363553635636357363583635936360363613636236363363643636536366363673636836369363703637136372363733637436375363763637736378363793638036381363823638336384363853638636387363883638936390363913639236393363943639536396363973639836399364003640136402364033640436405364063640736408364093641036411364123641336414364153641636417364183641936420364213642236423364243642536426364273642836429364303643136432364333643436435364363643736438364393644036441364423644336444364453644636447364483644936450364513645236453364543645536456364573645836459364603646136462364633646436465364663646736468364693647036471364723647336474364753647636477364783647936480364813648236483364843648536486364873648836489364903649136492364933649436495364963649736498364993650036501365023650336504365053650636507365083650936510365113651236513365143651536516365173651836519365203652136522365233652436525365263652736528365293653036531365323653336534365353653636537365383653936540365413654236543365443654536546365473654836549365503655136552365533655436555365563655736558365593656036561365623656336564365653656636567365683656936570365713657236573365743657536576365773657836579365803658136582365833658436585365863658736588365893659036591365923659336594365953659636597365983659936600366013660236603366043660536606366073660836609366103661136612366133661436615366163661736618366193662036621366223662336624366253662636627366283662936630366313663236633366343663536636366373663836639366403664136642366433664436645366463664736648366493665036651366523665336654366553665636657366583665936660366613666236663366643666536666366673666836669366703667136672366733667436675366763667736678366793668036681366823668336684366853668636687366883668936690366913669236693366943669536696366973669836699367003670136702367033670436705367063670736708367093671036711367123671336714367153671636717367183671936720367213672236723367243672536726367273672836729367303673136732367333673436735367363673736738367393674036741367423674336744367453674636747367483674936750367513675236753367543675536756367573675836759367603676136762367633676436765367663676736768367693677036771367723677336774367753677636777367783677936780367813678236783367843678536786367873678836789367903679136792367933679436795367963679736798367993680036801368023680336804368053680636807368083680936810368113681236813368143681536816368173681836819368203682136822368233682436825368263682736828368293683036831368323683336834368353683636837368383683936840368413684236843368443684536846368473684836849368503685136852368533685436855368563685736858368593686036861368623686336864368653686636867368683686936870368713687236873368743687536876368773687836879368803688136882368833688436885368863688736888368893689036891368923689336894368953689636897368983689936900369013690236903369043690536906369073690836909369103691136912369133691436915369163691736918369193692036921369223692336924369253692636927369283692936930369313693236933369343693536936369373693836939369403694136942369433694436945369463694736948369493695036951369523695336954369553695636957369583695936960369613696236963369643696536966369673696836969369703697136972369733697436975369763697736978369793698036981369823698336984369853698636987369883698936990369913699236993369943699536996369973699836999370003700137002370033700437005370063700737008370093701037011370123701337014370153701637017370183701937020370213702237023370243702537026370273702837029370303703137032370333703437035370363703737038370393704037041370423704337044370453704637047370483704937050370513705237053370543705537056370573705837059370603706137062370633706437065370663706737068370693707037071370723707337074370753707637077370783707937080370813708237083370843708537086370873708837089370903709137092370933709437095370963709737098370993710037101371023710337104371053710637107371083710937110371113711237113371143711537116371173711837119371203712137122371233712437125371263712737128371293713037131371323713337134371353713637137371383713937140371413714237143371443714537146371473714837149371503715137152371533715437155371563715737158371593716037161371623716337164371653716637167371683716937170371713717237173371743717537176371773717837179371803718137182371833718437185371863718737188371893719037191371923719337194371953719637197371983719937200372013720237203372043720537206372073720837209372103721137212372133721437215372163721737218372193722037221372223722337224372253722637227372283722937230372313723237233372343723537236372373723837239372403724137242372433724437245372463724737248372493725037251372523725337254372553725637257372583725937260372613726237263372643726537266372673726837269372703727137272372733727437275372763727737278372793728037281372823728337284372853728637287372883728937290372913729237293372943729537296372973729837299373003730137302373033730437305373063730737308373093731037311373123731337314373153731637317373183731937320373213732237323373243732537326373273732837329373303733137332373333733437335373363733737338373393734037341373423734337344373453734637347373483734937350373513735237353373543735537356373573735837359373603736137362373633736437365373663736737368373693737037371373723737337374373753737637377373783737937380373813738237383373843738537386373873738837389373903739137392373933739437395373963739737398373993740037401374023740337404374053740637407374083740937410374113741237413374143741537416374173741837419374203742137422374233742437425374263742737428374293743037431374323743337434374353743637437374383743937440374413744237443374443744537446374473744837449374503745137452374533745437455374563745737458374593746037461374623746337464374653746637467374683746937470374713747237473374743747537476374773747837479374803748137482374833748437485374863748737488374893749037491374923749337494374953749637497374983749937500375013750237503375043750537506375073750837509375103751137512375133751437515375163751737518375193752037521375223752337524375253752637527375283752937530375313753237533375343753537536375373753837539375403754137542375433754437545375463754737548375493755037551375523755337554375553755637557375583755937560375613756237563375643756537566375673756837569375703757137572375733757437575375763757737578375793758037581375823758337584375853758637587375883758937590375913759237593375943759537596375973759837599376003760137602376033760437605376063760737608376093761037611376123761337614376153761637617376183761937620376213762237623376243762537626376273762837629376303763137632376333763437635376363763737638376393764037641376423764337644376453764637647376483764937650376513765237653376543765537656376573765837659376603766137662376633766437665376663766737668376693767037671376723767337674376753767637677376783767937680376813768237683376843768537686376873768837689376903769137692376933769437695376963769737698376993770037701377023770337704377053770637707377083770937710377113771237713377143771537716377173771837719377203772137722377233772437725377263772737728377293773037731377323773337734377353773637737377383773937740377413774237743377443774537746377473774837749377503775137752377533775437755377563775737758377593776037761377623776337764377653776637767377683776937770377713777237773377743777537776377773777837779377803778137782377833778437785377863778737788377893779037791377923779337794377953779637797377983779937800378013780237803378043780537806378073780837809378103781137812378133781437815378163781737818378193782037821378223782337824378253782637827378283782937830378313783237833378343783537836378373783837839378403784137842378433784437845378463784737848378493785037851378523785337854378553785637857378583785937860378613786237863378643786537866378673786837869378703787137872378733787437875378763787737878378793788037881378823788337884378853788637887378883788937890378913789237893378943789537896378973789837899379003790137902379033790437905379063790737908379093791037911379123791337914379153791637917379183791937920379213792237923379243792537926379273792837929379303793137932379333793437935379363793737938379393794037941379423794337944379453794637947379483794937950379513795237953379543795537956379573795837959379603796137962379633796437965379663796737968379693797037971379723797337974379753797637977379783797937980379813798237983379843798537986379873798837989379903799137992379933799437995379963799737998379993800038001380023800338004380053800638007380083800938010380113801238013380143801538016380173801838019380203802138022380233802438025380263802738028380293803038031380323803338034380353803638037380383803938040380413804238043380443804538046380473804838049380503805138052380533805438055380563805738058380593806038061380623806338064380653806638067380683806938070380713807238073380743807538076380773807838079380803808138082380833808438085380863808738088380893809038091380923809338094380953809638097380983809938100381013810238103381043810538106381073810838109381103811138112381133811438115381163811738118381193812038121381223812338124381253812638127381283812938130381313813238133381343813538136381373813838139381403814138142381433814438145381463814738148381493815038151381523815338154381553815638157381583815938160381613816238163381643816538166381673816838169381703817138172381733817438175381763817738178381793818038181381823818338184381853818638187381883818938190381913819238193381943819538196381973819838199382003820138202382033820438205382063820738208382093821038211382123821338214382153821638217382183821938220382213822238223382243822538226382273822838229382303823138232382333823438235382363823738238382393824038241382423824338244382453824638247382483824938250382513825238253382543825538256382573825838259382603826138262382633826438265382663826738268382693827038271382723827338274382753827638277382783827938280382813828238283382843828538286382873828838289382903829138292382933829438295382963829738298382993830038301383023830338304383053830638307383083830938310383113831238313383143831538316383173831838319383203832138322383233832438325383263832738328383293833038331383323833338334383353833638337383383833938340383413834238343383443834538346383473834838349383503835138352383533835438355383563835738358383593836038361383623836338364383653836638367383683836938370383713837238373383743837538376383773837838379383803838138382383833838438385383863838738388383893839038391383923839338394383953839638397383983839938400384013840238403384043840538406384073840838409384103841138412384133841438415384163841738418384193842038421384223842338424384253842638427384283842938430384313843238433384343843538436384373843838439384403844138442384433844438445384463844738448384493845038451384523845338454384553845638457384583845938460384613846238463384643846538466384673846838469384703847138472384733847438475384763847738478384793848038481384823848338484384853848638487384883848938490384913849238493384943849538496384973849838499385003850138502385033850438505385063850738508385093851038511385123851338514385153851638517385183851938520385213852238523385243852538526385273852838529385303853138532385333853438535385363853738538385393854038541385423854338544385453854638547385483854938550385513855238553385543855538556385573855838559385603856138562385633856438565385663856738568385693857038571385723857338574385753857638577385783857938580385813858238583385843858538586385873858838589385903859138592385933859438595385963859738598385993860038601386023860338604386053860638607386083860938610386113861238613386143861538616386173861838619386203862138622386233862438625386263862738628386293863038631386323863338634386353863638637386383863938640386413864238643386443864538646386473864838649386503865138652386533865438655386563865738658386593866038661386623866338664386653866638667386683866938670386713867238673386743867538676386773867838679386803868138682386833868438685386863868738688386893869038691386923869338694386953869638697386983869938700387013870238703387043870538706387073870838709387103871138712387133871438715387163871738718387193872038721387223872338724387253872638727387283872938730387313873238733387343873538736387373873838739387403874138742387433874438745387463874738748387493875038751387523875338754387553875638757387583875938760387613876238763387643876538766387673876838769387703877138772387733877438775387763877738778387793878038781387823878338784387853878638787387883878938790387913879238793387943879538796387973879838799388003880138802388033880438805388063880738808388093881038811388123881338814388153881638817388183881938820388213882238823388243882538826388273882838829388303883138832388333883438835388363883738838388393884038841388423884338844388453884638847388483884938850388513885238853388543885538856388573885838859388603886138862388633886438865388663886738868388693887038871388723887338874388753887638877388783887938880388813888238883388843888538886388873888838889388903889138892388933889438895388963889738898388993890038901389023890338904389053890638907389083890938910389113891238913389143891538916389173891838919389203892138922389233892438925389263892738928389293893038931389323893338934389353893638937389383893938940389413894238943389443894538946389473894838949389503895138952389533895438955389563895738958389593896038961389623896338964389653896638967389683896938970389713897238973389743897538976389773897838979389803898138982389833898438985389863898738988389893899038991389923899338994389953899638997389983899939000390013900239003390043900539006390073900839009390103901139012390133901439015390163901739018390193902039021390223902339024390253902639027390283902939030390313903239033390343903539036390373903839039390403904139042390433904439045390463904739048390493905039051390523905339054390553905639057390583905939060390613906239063390643906539066390673906839069390703907139072390733907439075390763907739078390793908039081390823908339084390853908639087390883908939090390913909239093390943909539096390973909839099391003910139102391033910439105391063910739108391093911039111391123911339114391153911639117391183911939120391213912239123391243912539126391273912839129391303913139132391333913439135391363913739138391393914039141391423914339144391453914639147391483914939150391513915239153391543915539156391573915839159391603916139162391633916439165391663916739168391693917039171391723917339174391753917639177391783917939180391813918239183391843918539186391873918839189391903919139192391933919439195391963919739198391993920039201392023920339204392053920639207392083920939210392113921239213392143921539216392173921839219392203922139222392233922439225392263922739228392293923039231392323923339234392353923639237392383923939240392413924239243392443924539246392473924839249392503925139252392533925439255392563925739258392593926039261392623926339264392653926639267392683926939270392713927239273392743927539276392773927839279392803928139282392833928439285392863928739288392893929039291392923929339294392953929639297392983929939300393013930239303393043930539306393073930839309393103931139312393133931439315393163931739318393193932039321393223932339324393253932639327393283932939330393313933239333393343933539336393373933839339393403934139342393433934439345393463934739348393493935039351393523935339354393553935639357393583935939360393613936239363393643936539366393673936839369393703937139372393733937439375393763937739378393793938039381393823938339384393853938639387393883938939390393913939239393393943939539396393973939839399394003940139402394033940439405394063940739408394093941039411394123941339414394153941639417394183941939420394213942239423394243942539426394273942839429394303943139432394333943439435394363943739438394393944039441394423944339444394453944639447394483944939450394513945239453394543945539456394573945839459394603946139462394633946439465394663946739468394693947039471394723947339474394753947639477394783947939480394813948239483394843948539486394873948839489394903949139492394933949439495394963949739498394993950039501395023950339504395053950639507395083950939510395113951239513395143951539516395173951839519395203952139522395233952439525395263952739528395293953039531395323953339534395353953639537395383953939540395413954239543395443954539546395473954839549395503955139552395533955439555395563955739558395593956039561395623956339564395653956639567395683956939570395713957239573395743957539576395773957839579395803958139582395833958439585395863958739588395893959039591395923959339594395953959639597395983959939600396013960239603396043960539606396073960839609396103961139612396133961439615396163961739618396193962039621396223962339624396253962639627396283962939630396313963239633396343963539636396373963839639396403964139642396433964439645396463964739648396493965039651396523965339654396553965639657396583965939660396613966239663396643966539666396673966839669396703967139672396733967439675396763967739678396793968039681396823968339684396853968639687396883968939690396913969239693396943969539696396973969839699397003970139702397033970439705397063970739708397093971039711397123971339714397153971639717397183971939720397213972239723397243972539726397273972839729397303973139732397333973439735397363973739738397393974039741397423974339744397453974639747397483974939750397513975239753397543975539756397573975839759397603976139762397633976439765397663976739768397693977039771397723977339774397753977639777397783977939780397813978239783397843978539786397873978839789397903979139792397933979439795397963979739798397993980039801398023980339804398053980639807398083980939810398113981239813398143981539816398173981839819398203982139822398233982439825398263982739828398293983039831398323983339834398353983639837398383983939840398413984239843398443984539846398473984839849398503985139852398533985439855398563985739858398593986039861398623986339864398653986639867398683986939870398713987239873398743987539876398773987839879398803988139882398833988439885398863988739888398893989039891398923989339894398953989639897398983989939900399013990239903399043990539906399073990839909399103991139912399133991439915399163991739918399193992039921399223992339924399253992639927399283992939930399313993239933399343993539936399373993839939399403994139942399433994439945399463994739948399493995039951399523995339954399553995639957399583995939960399613996239963399643996539966399673996839969399703997139972399733997439975399763997739978399793998039981399823998339984399853998639987399883998939990399913999239993399943999539996399973999839999400004000140002400034000440005400064000740008400094001040011400124001340014400154001640017400184001940020400214002240023400244002540026400274002840029400304003140032400334003440035400364003740038400394004040041400424004340044400454004640047400484004940050400514005240053400544005540056400574005840059400604006140062400634006440065400664006740068400694007040071400724007340074400754007640077400784007940080400814008240083400844008540086400874008840089400904009140092400934009440095400964009740098400994010040101401024010340104401054010640107401084010940110401114011240113401144011540116401174011840119401204012140122401234012440125401264012740128401294013040131401324013340134401354013640137401384013940140401414014240143401444014540146401474014840149401504015140152401534015440155401564015740158401594016040161401624016340164401654016640167401684016940170401714017240173401744017540176401774017840179401804018140182401834018440185401864018740188401894019040191401924019340194401954019640197401984019940200402014020240203402044020540206402074020840209402104021140212402134021440215402164021740218402194022040221402224022340224402254022640227402284022940230402314023240233402344023540236402374023840239402404024140242402434024440245402464024740248402494025040251402524025340254402554025640257402584025940260402614026240263402644026540266402674026840269402704027140272402734027440275402764027740278402794028040281402824028340284402854028640287402884028940290402914029240293402944029540296402974029840299403004030140302403034030440305403064030740308403094031040311403124031340314403154031640317403184031940320403214032240323403244032540326403274032840329403304033140332403334033440335403364033740338403394034040341403424034340344403454034640347403484034940350403514035240353403544035540356403574035840359403604036140362403634036440365403664036740368403694037040371403724037340374403754037640377403784037940380403814038240383403844038540386403874038840389403904039140392403934039440395403964039740398403994040040401404024040340404404054040640407404084040940410404114041240413404144041540416404174041840419404204042140422404234042440425404264042740428404294043040431404324043340434404354043640437404384043940440404414044240443404444044540446404474044840449404504045140452404534045440455404564045740458404594046040461404624046340464404654046640467404684046940470404714047240473404744047540476404774047840479404804048140482404834048440485404864048740488404894049040491404924049340494404954049640497404984049940500405014050240503405044050540506405074050840509405104051140512405134051440515405164051740518405194052040521405224052340524405254052640527405284052940530405314053240533405344053540536405374053840539405404054140542405434054440545405464054740548405494055040551405524055340554405554055640557405584055940560405614056240563405644056540566405674056840569405704057140572405734057440575405764057740578405794058040581405824058340584405854058640587405884058940590405914059240593405944059540596405974059840599406004060140602406034060440605406064060740608406094061040611406124061340614406154061640617406184061940620406214062240623406244062540626406274062840629406304063140632406334063440635406364063740638406394064040641406424064340644406454064640647406484064940650406514065240653406544065540656406574065840659406604066140662406634066440665406664066740668406694067040671406724067340674406754067640677406784067940680406814068240683406844068540686406874068840689406904069140692406934069440695406964069740698406994070040701407024070340704407054070640707407084070940710407114071240713407144071540716407174071840719407204072140722407234072440725407264072740728407294073040731407324073340734407354073640737407384073940740407414074240743407444074540746407474074840749407504075140752407534075440755407564075740758407594076040761407624076340764407654076640767407684076940770407714077240773407744077540776407774077840779407804078140782407834078440785407864078740788407894079040791407924079340794407954079640797407984079940800408014080240803408044080540806408074080840809408104081140812408134081440815408164081740818408194082040821408224082340824408254082640827408284082940830408314083240833408344083540836408374083840839408404084140842408434084440845408464084740848408494085040851408524085340854408554085640857408584085940860408614086240863408644086540866408674086840869408704087140872408734087440875408764087740878408794088040881408824088340884408854088640887408884088940890408914089240893408944089540896408974089840899409004090140902409034090440905409064090740908409094091040911409124091340914409154091640917409184091940920409214092240923409244092540926409274092840929409304093140932409334093440935409364093740938409394094040941409424094340944409454094640947409484094940950409514095240953409544095540956409574095840959409604096140962409634096440965409664096740968409694097040971409724097340974409754097640977409784097940980409814098240983409844098540986409874098840989409904099140992409934099440995409964099740998409994100041001410024100341004410054100641007410084100941010410114101241013410144101541016410174101841019410204102141022410234102441025410264102741028410294103041031410324103341034410354103641037410384103941040410414104241043410444104541046410474104841049410504105141052410534105441055410564105741058410594106041061410624106341064410654106641067410684106941070410714107241073410744107541076410774107841079410804108141082410834108441085410864108741088410894109041091410924109341094410954109641097410984109941100411014110241103411044110541106411074110841109411104111141112411134111441115411164111741118411194112041121411224112341124411254112641127411284112941130411314113241133411344113541136411374113841139411404114141142411434114441145411464114741148411494115041151411524115341154411554115641157411584115941160411614116241163411644116541166411674116841169411704117141172411734117441175411764117741178411794118041181411824118341184411854118641187411884118941190411914119241193411944119541196411974119841199412004120141202412034120441205412064120741208412094121041211412124121341214412154121641217412184121941220412214122241223412244122541226412274122841229412304123141232412334123441235412364123741238412394124041241412424124341244412454124641247412484124941250412514125241253412544125541256412574125841259412604126141262412634126441265412664126741268412694127041271412724127341274412754127641277412784127941280412814128241283412844128541286412874128841289412904129141292412934129441295412964129741298412994130041301413024130341304413054130641307413084130941310413114131241313413144131541316413174131841319413204132141322413234132441325413264132741328413294133041331413324133341334413354133641337413384133941340413414134241343413444134541346413474134841349413504135141352413534135441355413564135741358413594136041361413624136341364413654136641367413684136941370413714137241373413744137541376413774137841379413804138141382413834138441385413864138741388413894139041391413924139341394413954139641397413984139941400414014140241403414044140541406414074140841409414104141141412414134141441415414164141741418414194142041421414224142341424414254142641427414284142941430414314143241433414344143541436414374143841439414404144141442414434144441445414464144741448414494145041451414524145341454414554145641457414584145941460414614146241463414644146541466414674146841469414704147141472414734147441475414764147741478414794148041481414824148341484414854148641487414884148941490414914149241493414944149541496414974149841499415004150141502415034150441505415064150741508415094151041511415124151341514415154151641517415184151941520415214152241523415244152541526415274152841529415304153141532415334153441535415364153741538415394154041541415424154341544415454154641547415484154941550415514155241553415544155541556415574155841559415604156141562415634156441565415664156741568415694157041571415724157341574415754157641577415784157941580415814158241583415844158541586415874158841589415904159141592415934159441595415964159741598415994160041601416024160341604416054160641607416084160941610416114161241613416144161541616416174161841619416204162141622416234162441625416264162741628416294163041631416324163341634416354163641637416384163941640416414164241643416444164541646416474164841649416504165141652416534165441655416564165741658416594166041661416624166341664416654166641667416684166941670416714167241673416744167541676416774167841679416804168141682416834168441685416864168741688416894169041691416924169341694416954169641697416984169941700417014170241703417044170541706417074170841709417104171141712417134171441715417164171741718417194172041721417224172341724417254172641727417284172941730417314173241733417344173541736417374173841739417404174141742417434174441745417464174741748417494175041751417524175341754417554175641757417584175941760417614176241763417644176541766417674176841769417704177141772417734177441775417764177741778417794178041781417824178341784417854178641787417884178941790417914179241793417944179541796417974179841799418004180141802418034180441805418064180741808418094181041811418124181341814418154181641817418184181941820418214182241823418244182541826418274182841829418304183141832418334183441835418364183741838418394184041841418424184341844418454184641847418484184941850418514185241853418544185541856418574185841859418604186141862418634186441865418664186741868418694187041871418724187341874418754187641877418784187941880418814188241883418844188541886418874188841889418904189141892418934189441895418964189741898418994190041901419024190341904419054190641907419084190941910419114191241913419144191541916419174191841919419204192141922419234192441925419264192741928419294193041931419324193341934419354193641937419384193941940419414194241943419444194541946419474194841949419504195141952419534195441955419564195741958419594196041961419624196341964419654196641967419684196941970419714197241973419744197541976419774197841979419804198141982419834198441985419864198741988419894199041991419924199341994419954199641997419984199942000420014200242003420044200542006420074200842009420104201142012420134201442015420164201742018420194202042021420224202342024420254202642027420284202942030420314203242033420344203542036420374203842039420404204142042420434204442045420464204742048420494205042051420524205342054420554205642057420584205942060420614206242063420644206542066420674206842069420704207142072420734207442075420764207742078420794208042081420824208342084420854208642087420884208942090420914209242093420944209542096420974209842099421004210142102421034210442105421064210742108421094211042111421124211342114421154211642117421184211942120421214212242123421244212542126421274212842129421304213142132421334213442135421364213742138421394214042141421424214342144421454214642147421484214942150421514215242153421544215542156421574215842159421604216142162421634216442165421664216742168421694217042171421724217342174421754217642177421784217942180421814218242183421844218542186421874218842189421904219142192421934219442195421964219742198421994220042201422024220342204422054220642207422084220942210422114221242213422144221542216422174221842219422204222142222422234222442225422264222742228422294223042231422324223342234422354223642237422384223942240422414224242243422444224542246422474224842249422504225142252422534225442255422564225742258422594226042261422624226342264422654226642267422684226942270422714227242273422744227542276422774227842279422804228142282422834228442285422864228742288422894229042291422924229342294422954229642297422984229942300423014230242303423044230542306423074230842309423104231142312423134231442315423164231742318423194232042321423224232342324423254232642327423284232942330423314233242333423344233542336423374233842339423404234142342423434234442345423464234742348423494235042351423524235342354423554235642357423584235942360423614236242363423644236542366423674236842369423704237142372423734237442375423764237742378423794238042381423824238342384423854238642387423884238942390423914239242393423944239542396423974239842399424004240142402424034240442405424064240742408424094241042411424124241342414424154241642417424184241942420424214242242423424244242542426424274242842429424304243142432424334243442435424364243742438424394244042441424424244342444424454244642447424484244942450424514245242453424544245542456424574245842459424604246142462424634246442465424664246742468424694247042471424724247342474424754247642477424784247942480424814248242483424844248542486424874248842489424904249142492424934249442495424964249742498424994250042501425024250342504425054250642507425084250942510425114251242513425144251542516425174251842519425204252142522425234252442525425264252742528425294253042531425324253342534425354253642537425384253942540425414254242543425444254542546425474254842549425504255142552425534255442555425564255742558425594256042561425624256342564425654256642567425684256942570425714257242573425744257542576425774257842579425804258142582425834258442585425864258742588425894259042591425924259342594425954259642597425984259942600426014260242603426044260542606426074260842609426104261142612426134261442615426164261742618426194262042621426224262342624426254262642627426284262942630426314263242633426344263542636426374263842639426404264142642426434264442645426464264742648426494265042651426524265342654426554265642657426584265942660426614266242663426644266542666426674266842669426704267142672426734267442675426764267742678426794268042681426824268342684426854268642687426884268942690426914269242693426944269542696426974269842699427004270142702427034270442705427064270742708427094271042711427124271342714427154271642717427184271942720427214272242723427244272542726427274272842729427304273142732427334273442735427364273742738427394274042741427424274342744427454274642747427484274942750427514275242753427544275542756427574275842759427604276142762427634276442765427664276742768427694277042771427724277342774427754277642777427784277942780427814278242783427844278542786427874278842789427904279142792427934279442795427964279742798427994280042801428024280342804428054280642807428084280942810428114281242813428144281542816428174281842819428204282142822428234282442825428264282742828428294283042831428324283342834428354283642837428384283942840428414284242843428444284542846428474284842849428504285142852428534285442855428564285742858428594286042861428624286342864428654286642867428684286942870428714287242873428744287542876428774287842879428804288142882428834288442885428864288742888428894289042891428924289342894428954289642897428984289942900429014290242903429044290542906429074290842909429104291142912429134291442915429164291742918429194292042921429224292342924429254292642927429284292942930429314293242933429344293542936429374293842939429404294142942429434294442945429464294742948429494295042951429524295342954429554295642957429584295942960429614296242963429644296542966429674296842969429704297142972429734297442975429764297742978429794298042981429824298342984429854298642987429884298942990429914299242993429944299542996429974299842999430004300143002430034300443005430064300743008430094301043011430124301343014430154301643017430184301943020430214302243023430244302543026430274302843029430304303143032430334303443035430364303743038430394304043041430424304343044430454304643047430484304943050430514305243053430544305543056430574305843059430604306143062430634306443065430664306743068430694307043071430724307343074430754307643077430784307943080430814308243083430844308543086430874308843089430904309143092430934309443095430964309743098430994310043101431024310343104431054310643107431084310943110431114311243113431144311543116431174311843119431204312143122431234312443125431264312743128431294313043131431324313343134431354313643137431384313943140431414314243143431444314543146431474314843149431504315143152431534315443155431564315743158431594316043161431624316343164431654316643167431684316943170431714317243173431744317543176431774317843179431804318143182431834318443185431864318743188431894319043191431924319343194431954319643197431984319943200432014320243203432044320543206432074320843209432104321143212432134321443215432164321743218432194322043221432224322343224432254322643227432284322943230432314323243233432344323543236432374323843239432404324143242432434324443245432464324743248432494325043251432524325343254432554325643257432584325943260432614326243263432644326543266432674326843269432704327143272432734327443275432764327743278432794328043281432824328343284432854328643287432884328943290432914329243293432944329543296432974329843299433004330143302433034330443305433064330743308433094331043311433124331343314433154331643317433184331943320433214332243323433244332543326433274332843329433304333143332433334333443335433364333743338433394334043341433424334343344433454334643347433484334943350433514335243353433544335543356433574335843359433604336143362433634336443365433664336743368433694337043371433724337343374433754337643377433784337943380433814338243383433844338543386433874338843389433904339143392433934339443395433964339743398433994340043401434024340343404434054340643407434084340943410434114341243413434144341543416434174341843419434204342143422434234342443425434264342743428434294343043431434324343343434434354343643437434384343943440434414344243443434444344543446434474344843449434504345143452434534345443455434564345743458434594346043461434624346343464434654346643467434684346943470434714347243473434744347543476434774347843479434804348143482434834348443485434864348743488434894349043491434924349343494434954349643497434984349943500435014350243503435044350543506435074350843509435104351143512435134351443515435164351743518435194352043521435224352343524435254352643527435284352943530435314353243533435344353543536435374353843539435404354143542435434354443545435464354743548435494355043551435524355343554435554355643557435584355943560435614356243563435644356543566435674356843569435704357143572435734357443575435764357743578435794358043581435824358343584435854358643587435884358943590435914359243593435944359543596435974359843599436004360143602436034360443605436064360743608436094361043611436124361343614436154361643617436184361943620436214362243623436244362543626436274362843629436304363143632436334363443635436364363743638436394364043641436424364343644436454364643647436484364943650436514365243653436544365543656436574365843659436604366143662436634366443665436664366743668436694367043671436724367343674436754367643677436784367943680436814368243683436844368543686436874368843689436904369143692436934369443695436964369743698436994370043701437024370343704437054370643707437084370943710437114371243713437144371543716437174371843719437204372143722437234372443725437264372743728437294373043731437324373343734437354373643737437384373943740437414374243743437444374543746437474374843749437504375143752437534375443755437564375743758437594376043761437624376343764437654376643767437684376943770437714377243773437744377543776437774377843779437804378143782437834378443785437864378743788437894379043791437924379343794437954379643797437984379943800438014380243803438044380543806438074380843809438104381143812438134381443815438164381743818438194382043821438224382343824438254382643827438284382943830438314383243833438344383543836438374383843839438404384143842438434384443845438464384743848438494385043851438524385343854438554385643857438584385943860438614386243863438644386543866438674386843869438704387143872438734387443875438764387743878438794388043881438824388343884438854388643887438884388943890438914389243893438944389543896438974389843899439004390143902439034390443905439064390743908439094391043911439124391343914439154391643917439184391943920439214392243923439244392543926439274392843929439304393143932439334393443935439364393743938439394394043941439424394343944439454394643947439484394943950439514395243953439544395543956439574395843959439604396143962439634396443965439664396743968439694397043971439724397343974439754397643977439784397943980439814398243983439844398543986439874398843989439904399143992439934399443995439964399743998439994400044001440024400344004440054400644007440084400944010440114401244013440144401544016440174401844019440204402144022440234402444025440264402744028440294403044031440324403344034440354403644037440384403944040440414404244043440444404544046440474404844049440504405144052440534405444055440564405744058440594406044061440624406344064440654406644067440684406944070440714407244073440744407544076440774407844079440804408144082440834408444085440864408744088440894409044091440924409344094440954409644097440984409944100441014410244103441044410544106441074410844109441104411144112441134411444115441164411744118441194412044121441224412344124441254412644127441284412944130441314413244133441344413544136441374413844139441404414144142441434414444145441464414744148441494415044151441524415344154441554415644157441584415944160441614416244163441644416544166441674416844169441704417144172441734417444175441764417744178441794418044181441824418344184441854418644187441884418944190441914419244193441944419544196441974419844199442004420144202442034420444205442064420744208442094421044211442124421344214442154421644217442184421944220442214422244223442244422544226442274422844229442304423144232442334423444235442364423744238442394424044241442424424344244442454424644247442484424944250442514425244253442544425544256442574425844259442604426144262442634426444265442664426744268442694427044271442724427344274442754427644277442784427944280442814428244283442844428544286442874428844289442904429144292442934429444295442964429744298442994430044301443024430344304443054430644307443084430944310443114431244313443144431544316443174431844319443204432144322443234432444325443264432744328443294433044331443324433344334443354433644337443384433944340443414434244343443444434544346443474434844349443504435144352443534435444355443564435744358443594436044361443624436344364443654436644367443684436944370443714437244373443744437544376443774437844379443804438144382443834438444385443864438744388443894439044391443924439344394443954439644397443984439944400444014440244403444044440544406444074440844409444104441144412444134441444415444164441744418444194442044421444224442344424444254442644427444284442944430444314443244433444344443544436444374443844439444404444144442444434444444445444464444744448444494445044451444524445344454444554445644457444584445944460444614446244463444644446544466444674446844469444704447144472444734447444475444764447744478444794448044481444824448344484444854448644487444884448944490444914449244493444944449544496444974449844499445004450144502445034450444505445064450744508445094451044511445124451344514445154451644517445184451944520445214452244523445244452544526445274452844529445304453144532445334453444535445364453744538445394454044541445424454344544445454454644547445484454944550445514455244553445544455544556445574455844559445604456144562445634456444565445664456744568445694457044571445724457344574445754457644577445784457944580445814458244583445844458544586445874458844589445904459144592445934459444595445964459744598445994460044601446024460344604446054460644607446084460944610446114461244613446144461544616446174461844619446204462144622446234462444625446264462744628446294463044631446324463344634446354463644637446384463944640446414464244643446444464544646446474464844649446504465144652446534465444655446564465744658446594466044661446624466344664446654466644667446684466944670446714467244673446744467544676446774467844679446804468144682446834468444685446864468744688446894469044691446924469344694446954469644697446984469944700447014470244703447044470544706447074470844709447104471144712447134471444715447164471744718447194472044721447224472344724447254472644727447284472944730447314473244733447344473544736447374473844739447404474144742447434474444745447464474744748447494475044751447524475344754447554475644757447584475944760447614476244763447644476544766447674476844769447704477144772447734477444775447764477744778447794478044781447824478344784447854478644787447884478944790447914479244793447944479544796447974479844799448004480144802448034480444805448064480744808448094481044811448124481344814448154481644817448184481944820448214482244823448244482544826448274482844829448304483144832448334483444835448364483744838448394484044841448424484344844448454484644847448484484944850448514485244853448544485544856448574485844859448604486144862448634486444865448664486744868448694487044871448724487344874448754487644877448784487944880448814488244883448844488544886448874488844889448904489144892448934489444895448964489744898448994490044901449024490344904449054490644907449084490944910449114491244913449144491544916449174491844919449204492144922449234492444925449264492744928449294493044931449324493344934449354493644937449384493944940449414494244943449444494544946449474494844949449504495144952449534495444955449564495744958449594496044961449624496344964449654496644967449684496944970449714497244973449744497544976449774497844979449804498144982449834498444985449864498744988449894499044991449924499344994449954499644997449984499945000450014500245003450044500545006450074500845009450104501145012450134501445015450164501745018450194502045021450224502345024450254502645027450284502945030450314503245033450344503545036450374503845039450404504145042450434504445045450464504745048450494505045051450524505345054450554505645057450584505945060450614506245063450644506545066450674506845069450704507145072450734507445075450764507745078450794508045081450824508345084450854508645087450884508945090450914509245093450944509545096450974509845099451004510145102451034510445105451064510745108451094511045111451124511345114451154511645117451184511945120451214512245123451244512545126451274512845129451304513145132451334513445135451364513745138451394514045141451424514345144451454514645147451484514945150451514515245153451544515545156451574515845159451604516145162451634516445165451664516745168451694517045171451724517345174451754517645177451784517945180451814518245183451844518545186451874518845189451904519145192451934519445195451964519745198451994520045201452024520345204452054520645207452084520945210452114521245213452144521545216452174521845219452204522145222452234522445225452264522745228452294523045231452324523345234452354523645237452384523945240452414524245243452444524545246452474524845249452504525145252452534525445255452564525745258452594526045261452624526345264452654526645267452684526945270452714527245273452744527545276452774527845279452804528145282452834528445285452864528745288452894529045291452924529345294452954529645297452984529945300453014530245303453044530545306453074530845309453104531145312453134531445315453164531745318453194532045321453224532345324453254532645327453284532945330453314533245333453344533545336453374533845339453404534145342453434534445345453464534745348453494535045351453524535345354453554535645357453584535945360453614536245363453644536545366453674536845369453704537145372453734537445375453764537745378453794538045381
  1. /**
  2. * @license
  3. * Video.js 7.5.4 <http://videojs.com/>
  4. * Copyright Brightcove, Inc. <https://www.brightcove.com/>
  5. * Available under Apache License Version 2.0
  6. * <https://github.com/videojs/video.js/blob/master/LICENSE>
  7. *
  8. * Includes vtt.js <https://github.com/mozilla/vtt.js>
  9. * Available under Apache License Version 2.0
  10. * <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
  11. */
  12. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  13. var window$1 = _interopDefault(require('global/window'));
  14. var document = _interopDefault(require('global/document'));
  15. var tsml = _interopDefault(require('tsml'));
  16. var safeParseTuple = _interopDefault(require('safe-json-parse/tuple'));
  17. var keycode = _interopDefault(require('keycode'));
  18. var xhr = _interopDefault(require('xhr'));
  19. var vtt = _interopDefault(require('videojs-vtt.js'));
  20. var URLToolkit = _interopDefault(require('url-toolkit'));
  21. var m3u8Parser = require('m3u8-parser');
  22. var mpdParser = require('mpd-parser');
  23. var mp4Inspector = _interopDefault(require('mux.js/lib/tools/mp4-inspector'));
  24. var mp4probe = _interopDefault(require('mux.js/lib/mp4/probe'));
  25. var mp4 = require('mux.js/lib/mp4');
  26. var tsInspector = _interopDefault(require('mux.js/lib/tools/ts-inspector.js'));
  27. var aesDecrypter = require('aes-decrypter');
  28. var version = "7.5.4";
  29. function _inheritsLoose(subClass, superClass) {
  30. subClass.prototype = Object.create(superClass.prototype);
  31. subClass.prototype.constructor = subClass;
  32. subClass.__proto__ = superClass;
  33. }
  34. function _setPrototypeOf(o, p) {
  35. _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
  36. o.__proto__ = p;
  37. return o;
  38. };
  39. return _setPrototypeOf(o, p);
  40. }
  41. function isNativeReflectConstruct() {
  42. if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  43. if (Reflect.construct.sham) return false;
  44. if (typeof Proxy === "function") return true;
  45. try {
  46. Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
  47. return true;
  48. } catch (e) {
  49. return false;
  50. }
  51. }
  52. function _construct(Parent, args, Class) {
  53. if (isNativeReflectConstruct()) {
  54. _construct = Reflect.construct;
  55. } else {
  56. _construct = function _construct(Parent, args, Class) {
  57. var a = [null];
  58. a.push.apply(a, args);
  59. var Constructor = Function.bind.apply(Parent, a);
  60. var instance = new Constructor();
  61. if (Class) _setPrototypeOf(instance, Class.prototype);
  62. return instance;
  63. };
  64. }
  65. return _construct.apply(null, arguments);
  66. }
  67. function _assertThisInitialized(self) {
  68. if (self === void 0) {
  69. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  70. }
  71. return self;
  72. }
  73. function _taggedTemplateLiteralLoose(strings, raw) {
  74. if (!raw) {
  75. raw = strings.slice(0);
  76. }
  77. strings.raw = raw;
  78. return strings;
  79. }
  80. /**
  81. * @file create-logger.js
  82. * @module create-logger
  83. */
  84. var history = [];
  85. /**
  86. * Log messages to the console and history based on the type of message
  87. *
  88. * @private
  89. * @param {string} type
  90. * The name of the console method to use.
  91. *
  92. * @param {Array} args
  93. * The arguments to be passed to the matching console method.
  94. */
  95. var LogByTypeFactory = function LogByTypeFactory(name, log) {
  96. return function (type, level, args) {
  97. var lvl = log.levels[level];
  98. var lvlRegExp = new RegExp("^(" + lvl + ")$");
  99. if (type !== 'log') {
  100. // Add the type to the front of the message when it's not "log".
  101. args.unshift(type.toUpperCase() + ':');
  102. } // Add console prefix after adding to history.
  103. args.unshift(name + ':'); // Add a clone of the args at this point to history.
  104. if (history) {
  105. history.push([].concat(args));
  106. } // If there's no console then don't try to output messages, but they will
  107. // still be stored in history.
  108. if (!window$1.console) {
  109. return;
  110. } // Was setting these once outside of this function, but containing them
  111. // in the function makes it easier to test cases where console doesn't exist
  112. // when the module is executed.
  113. var fn = window$1.console[type];
  114. if (!fn && type === 'debug') {
  115. // Certain browsers don't have support for console.debug. For those, we
  116. // should default to the closest comparable log.
  117. fn = window$1.console.info || window$1.console.log;
  118. } // Bail out if there's no console or if this type is not allowed by the
  119. // current logging level.
  120. if (!fn || !lvl || !lvlRegExp.test(type)) {
  121. return;
  122. }
  123. fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
  124. };
  125. };
  126. function createLogger(name) {
  127. // This is the private tracking variable for logging level.
  128. var level = 'info'; // the curried logByType bound to the specific log and history
  129. var logByType;
  130. /**
  131. * Logs plain debug messages. Similar to `console.log`.
  132. *
  133. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  134. * of our JSDoc template, we cannot properly document this as both a function
  135. * and a namespace, so its function signature is documented here.
  136. *
  137. * #### Arguments
  138. * ##### *args
  139. * Mixed[]
  140. *
  141. * Any combination of values that could be passed to `console.log()`.
  142. *
  143. * #### Return Value
  144. *
  145. * `undefined`
  146. *
  147. * @namespace
  148. * @param {Mixed[]} args
  149. * One or more messages or objects that should be logged.
  150. */
  151. var log = function log() {
  152. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  153. args[_key] = arguments[_key];
  154. }
  155. logByType('log', level, args);
  156. }; // This is the logByType helper that the logging methods below use
  157. logByType = LogByTypeFactory(name, log);
  158. /**
  159. * Create a new sublogger which chains the old name to the new name.
  160. *
  161. * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
  162. * ```js
  163. * mylogger('foo');
  164. * // > VIDEOJS: player: foo
  165. * ```
  166. *
  167. * @param {string} name
  168. * The name to add call the new logger
  169. * @return {Object}
  170. */
  171. log.createLogger = function (subname) {
  172. return createLogger(name + ': ' + subname);
  173. };
  174. /**
  175. * Enumeration of available logging levels, where the keys are the level names
  176. * and the values are `|`-separated strings containing logging methods allowed
  177. * in that logging level. These strings are used to create a regular expression
  178. * matching the function name being called.
  179. *
  180. * Levels provided by Video.js are:
  181. *
  182. * - `off`: Matches no calls. Any value that can be cast to `false` will have
  183. * this effect. The most restrictive.
  184. * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
  185. * `log.warn`, and `log.error`).
  186. * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
  187. * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
  188. * - `warn`: Matches `log.warn` and `log.error` calls.
  189. * - `error`: Matches only `log.error` calls.
  190. *
  191. * @type {Object}
  192. */
  193. log.levels = {
  194. all: 'debug|log|warn|error',
  195. off: '',
  196. debug: 'debug|log|warn|error',
  197. info: 'log|warn|error',
  198. warn: 'warn|error',
  199. error: 'error',
  200. DEFAULT: level
  201. };
  202. /**
  203. * Get or set the current logging level.
  204. *
  205. * If a string matching a key from {@link module:log.levels} is provided, acts
  206. * as a setter.
  207. *
  208. * @param {string} [lvl]
  209. * Pass a valid level to set a new logging level.
  210. *
  211. * @return {string}
  212. * The current logging level.
  213. */
  214. log.level = function (lvl) {
  215. if (typeof lvl === 'string') {
  216. if (!log.levels.hasOwnProperty(lvl)) {
  217. throw new Error("\"" + lvl + "\" in not a valid log level");
  218. }
  219. level = lvl;
  220. }
  221. return level;
  222. };
  223. /**
  224. * Returns an array containing everything that has been logged to the history.
  225. *
  226. * This array is a shallow clone of the internal history record. However, its
  227. * contents are _not_ cloned; so, mutating objects inside this array will
  228. * mutate them in history.
  229. *
  230. * @return {Array}
  231. */
  232. log.history = function () {
  233. return history ? [].concat(history) : [];
  234. };
  235. /**
  236. * Allows you to filter the history by the given logger name
  237. *
  238. * @param {string} fname
  239. * The name to filter by
  240. *
  241. * @return {Array}
  242. * The filtered list to return
  243. */
  244. log.history.filter = function (fname) {
  245. return (history || []).filter(function (historyItem) {
  246. // if the first item in each historyItem includes `fname`, then it's a match
  247. return new RegExp(".*" + fname + ".*").test(historyItem[0]);
  248. });
  249. };
  250. /**
  251. * Clears the internal history tracking, but does not prevent further history
  252. * tracking.
  253. */
  254. log.history.clear = function () {
  255. if (history) {
  256. history.length = 0;
  257. }
  258. };
  259. /**
  260. * Disable history tracking if it is currently enabled.
  261. */
  262. log.history.disable = function () {
  263. if (history !== null) {
  264. history.length = 0;
  265. history = null;
  266. }
  267. };
  268. /**
  269. * Enable history tracking if it is currently disabled.
  270. */
  271. log.history.enable = function () {
  272. if (history === null) {
  273. history = [];
  274. }
  275. };
  276. /**
  277. * Logs error messages. Similar to `console.error`.
  278. *
  279. * @param {Mixed[]} args
  280. * One or more messages or objects that should be logged as an error
  281. */
  282. log.error = function () {
  283. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  284. args[_key2] = arguments[_key2];
  285. }
  286. return logByType('error', level, args);
  287. };
  288. /**
  289. * Logs warning messages. Similar to `console.warn`.
  290. *
  291. * @param {Mixed[]} args
  292. * One or more messages or objects that should be logged as a warning.
  293. */
  294. log.warn = function () {
  295. for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  296. args[_key3] = arguments[_key3];
  297. }
  298. return logByType('warn', level, args);
  299. };
  300. /**
  301. * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
  302. * log if `console.debug` is not available
  303. *
  304. * @param {Mixed[]} args
  305. * One or more messages or objects that should be logged as debug.
  306. */
  307. log.debug = function () {
  308. for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  309. args[_key4] = arguments[_key4];
  310. }
  311. return logByType('debug', level, args);
  312. };
  313. return log;
  314. }
  315. /**
  316. * @file log.js
  317. * @module log
  318. */
  319. var log = createLogger('VIDEOJS');
  320. var createLogger$1 = log.createLogger;
  321. /**
  322. * @file obj.js
  323. * @module obj
  324. */
  325. /**
  326. * @callback obj:EachCallback
  327. *
  328. * @param {Mixed} value
  329. * The current key for the object that is being iterated over.
  330. *
  331. * @param {string} key
  332. * The current key-value for object that is being iterated over
  333. */
  334. /**
  335. * @callback obj:ReduceCallback
  336. *
  337. * @param {Mixed} accum
  338. * The value that is accumulating over the reduce loop.
  339. *
  340. * @param {Mixed} value
  341. * The current key for the object that is being iterated over.
  342. *
  343. * @param {string} key
  344. * The current key-value for object that is being iterated over
  345. *
  346. * @return {Mixed}
  347. * The new accumulated value.
  348. */
  349. var toString = Object.prototype.toString;
  350. /**
  351. * Get the keys of an Object
  352. *
  353. * @param {Object}
  354. * The Object to get the keys from
  355. *
  356. * @return {string[]}
  357. * An array of the keys from the object. Returns an empty array if the
  358. * object passed in was invalid or had no keys.
  359. *
  360. * @private
  361. */
  362. var keys = function keys(object) {
  363. return isObject(object) ? Object.keys(object) : [];
  364. };
  365. /**
  366. * Array-like iteration for objects.
  367. *
  368. * @param {Object} object
  369. * The object to iterate over
  370. *
  371. * @param {obj:EachCallback} fn
  372. * The callback function which is called for each key in the object.
  373. */
  374. function each(object, fn) {
  375. keys(object).forEach(function (key) {
  376. return fn(object[key], key);
  377. });
  378. }
  379. /**
  380. * Array-like reduce for objects.
  381. *
  382. * @param {Object} object
  383. * The Object that you want to reduce.
  384. *
  385. * @param {Function} fn
  386. * A callback function which is called for each key in the object. It
  387. * receives the accumulated value and the per-iteration value and key
  388. * as arguments.
  389. *
  390. * @param {Mixed} [initial = 0]
  391. * Starting value
  392. *
  393. * @return {Mixed}
  394. * The final accumulated value.
  395. */
  396. function reduce(object, fn, initial) {
  397. if (initial === void 0) {
  398. initial = 0;
  399. }
  400. return keys(object).reduce(function (accum, key) {
  401. return fn(accum, object[key], key);
  402. }, initial);
  403. }
  404. /**
  405. * Object.assign-style object shallow merge/extend.
  406. *
  407. * @param {Object} target
  408. * @param {Object} ...sources
  409. * @return {Object}
  410. */
  411. function assign(target) {
  412. for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  413. sources[_key - 1] = arguments[_key];
  414. }
  415. if (Object.assign) {
  416. return Object.assign.apply(Object, [target].concat(sources));
  417. }
  418. sources.forEach(function (source) {
  419. if (!source) {
  420. return;
  421. }
  422. each(source, function (value, key) {
  423. target[key] = value;
  424. });
  425. });
  426. return target;
  427. }
  428. /**
  429. * Returns whether a value is an object of any kind - including DOM nodes,
  430. * arrays, regular expressions, etc. Not functions, though.
  431. *
  432. * This avoids the gotcha where using `typeof` on a `null` value
  433. * results in `'object'`.
  434. *
  435. * @param {Object} value
  436. * @return {boolean}
  437. */
  438. function isObject(value) {
  439. return !!value && typeof value === 'object';
  440. }
  441. /**
  442. * Returns whether an object appears to be a "plain" object - that is, a
  443. * direct instance of `Object`.
  444. *
  445. * @param {Object} value
  446. * @return {boolean}
  447. */
  448. function isPlain(value) {
  449. return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
  450. }
  451. /**
  452. * @file computed-style.js
  453. * @module computed-style
  454. */
  455. /**
  456. * A safe getComputedStyle.
  457. *
  458. * This is needed because in Firefox, if the player is loaded in an iframe with
  459. * `display:none`, then `getComputedStyle` returns `null`, so, we do a
  460. * null-check to make sure that the player doesn't break in these cases.
  461. *
  462. * @function
  463. * @param {Element} el
  464. * The element you want the computed style of
  465. *
  466. * @param {string} prop
  467. * The property name you want
  468. *
  469. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  470. */
  471. function computedStyle(el, prop) {
  472. if (!el || !prop) {
  473. return '';
  474. }
  475. if (typeof window$1.getComputedStyle === 'function') {
  476. var cs = window$1.getComputedStyle(el);
  477. return cs ? cs[prop] : '';
  478. }
  479. return '';
  480. }
  481. function _templateObject() {
  482. 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 ", "."]);
  483. _templateObject = function _templateObject() {
  484. return data;
  485. };
  486. return data;
  487. }
  488. /**
  489. * Detect if a value is a string with any non-whitespace characters.
  490. *
  491. * @private
  492. * @param {string} str
  493. * The string to check
  494. *
  495. * @return {boolean}
  496. * Will be `true` if the string is non-blank, `false` otherwise.
  497. *
  498. */
  499. function isNonBlankString(str) {
  500. return typeof str === 'string' && /\S/.test(str);
  501. }
  502. /**
  503. * Throws an error if the passed string has whitespace. This is used by
  504. * class methods to be relatively consistent with the classList API.
  505. *
  506. * @private
  507. * @param {string} str
  508. * The string to check for whitespace.
  509. *
  510. * @throws {Error}
  511. * Throws an error if there is whitespace in the string.
  512. */
  513. function throwIfWhitespace(str) {
  514. if (/\s/.test(str)) {
  515. throw new Error('class has illegal whitespace characters');
  516. }
  517. }
  518. /**
  519. * Produce a regular expression for matching a className within an elements className.
  520. *
  521. * @private
  522. * @param {string} className
  523. * The className to generate the RegExp for.
  524. *
  525. * @return {RegExp}
  526. * The RegExp that will check for a specific `className` in an elements
  527. * className.
  528. */
  529. function classRegExp(className) {
  530. return new RegExp('(^|\\s)' + className + '($|\\s)');
  531. }
  532. /**
  533. * Whether the current DOM interface appears to be real (i.e. not simulated).
  534. *
  535. * @return {boolean}
  536. * Will be `true` if the DOM appears to be real, `false` otherwise.
  537. */
  538. function isReal() {
  539. // Both document and window will never be undefined thanks to `global`.
  540. return document === window$1.document;
  541. }
  542. /**
  543. * Determines, via duck typing, whether or not a value is a DOM element.
  544. *
  545. * @param {Mixed} value
  546. * The value to check.
  547. *
  548. * @return {boolean}
  549. * Will be `true` if the value is a DOM element, `false` otherwise.
  550. */
  551. function isEl(value) {
  552. return isObject(value) && value.nodeType === 1;
  553. }
  554. /**
  555. * Determines if the current DOM is embedded in an iframe.
  556. *
  557. * @return {boolean}
  558. * Will be `true` if the DOM is embedded in an iframe, `false`
  559. * otherwise.
  560. */
  561. function isInFrame() {
  562. // We need a try/catch here because Safari will throw errors when attempting
  563. // to get either `parent` or `self`
  564. try {
  565. return window$1.parent !== window$1.self;
  566. } catch (x) {
  567. return true;
  568. }
  569. }
  570. /**
  571. * Creates functions to query the DOM using a given method.
  572. *
  573. * @private
  574. * @param {string} method
  575. * The method to create the query with.
  576. *
  577. * @return {Function}
  578. * The query method
  579. */
  580. function createQuerier(method) {
  581. return function (selector, context) {
  582. if (!isNonBlankString(selector)) {
  583. return document[method](null);
  584. }
  585. if (isNonBlankString(context)) {
  586. context = document.querySelector(context);
  587. }
  588. var ctx = isEl(context) ? context : document;
  589. return ctx[method] && ctx[method](selector);
  590. };
  591. }
  592. /**
  593. * Creates an element and applies properties, attributes, and inserts content.
  594. *
  595. * @param {string} [tagName='div']
  596. * Name of tag to be created.
  597. *
  598. * @param {Object} [properties={}]
  599. * Element properties to be applied.
  600. *
  601. * @param {Object} [attributes={}]
  602. * Element attributes to be applied.
  603. *
  604. * @param {module:dom~ContentDescriptor} content
  605. * A content descriptor object.
  606. *
  607. * @return {Element}
  608. * The element that was created.
  609. */
  610. function createEl(tagName, properties, attributes, content) {
  611. if (tagName === void 0) {
  612. tagName = 'div';
  613. }
  614. if (properties === void 0) {
  615. properties = {};
  616. }
  617. if (attributes === void 0) {
  618. attributes = {};
  619. }
  620. var el = document.createElement(tagName);
  621. Object.getOwnPropertyNames(properties).forEach(function (propName) {
  622. var val = properties[propName]; // See #2176
  623. // We originally were accepting both properties and attributes in the
  624. // same object, but that doesn't work so well.
  625. if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
  626. log.warn(tsml(_templateObject(), propName, val));
  627. el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
  628. // method for it.
  629. } else if (propName === 'textContent') {
  630. textContent(el, val);
  631. } else {
  632. el[propName] = val;
  633. }
  634. });
  635. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  636. el.setAttribute(attrName, attributes[attrName]);
  637. });
  638. if (content) {
  639. appendContent(el, content);
  640. }
  641. return el;
  642. }
  643. /**
  644. * Injects text into an element, replacing any existing contents entirely.
  645. *
  646. * @param {Element} el
  647. * The element to add text content into
  648. *
  649. * @param {string} text
  650. * The text content to add.
  651. *
  652. * @return {Element}
  653. * The element with added text content.
  654. */
  655. function textContent(el, text) {
  656. if (typeof el.textContent === 'undefined') {
  657. el.innerText = text;
  658. } else {
  659. el.textContent = text;
  660. }
  661. return el;
  662. }
  663. /**
  664. * Insert an element as the first child node of another
  665. *
  666. * @param {Element} child
  667. * Element to insert
  668. *
  669. * @param {Element} parent
  670. * Element to insert child into
  671. */
  672. function prependTo(child, parent) {
  673. if (parent.firstChild) {
  674. parent.insertBefore(child, parent.firstChild);
  675. } else {
  676. parent.appendChild(child);
  677. }
  678. }
  679. /**
  680. * Check if an element has a class name.
  681. *
  682. * @param {Element} element
  683. * Element to check
  684. *
  685. * @param {string} classToCheck
  686. * Class name to check for
  687. *
  688. * @return {boolean}
  689. * Will be `true` if the element has a class, `false` otherwise.
  690. *
  691. * @throws {Error}
  692. * Throws an error if `classToCheck` has white space.
  693. */
  694. function hasClass(element, classToCheck) {
  695. throwIfWhitespace(classToCheck);
  696. if (element.classList) {
  697. return element.classList.contains(classToCheck);
  698. }
  699. return classRegExp(classToCheck).test(element.className);
  700. }
  701. /**
  702. * Add a class name to an element.
  703. *
  704. * @param {Element} element
  705. * Element to add class name to.
  706. *
  707. * @param {string} classToAdd
  708. * Class name to add.
  709. *
  710. * @return {Element}
  711. * The DOM element with the added class name.
  712. */
  713. function addClass(element, classToAdd) {
  714. if (element.classList) {
  715. element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
  716. // in the case of classList not being supported.
  717. } else if (!hasClass(element, classToAdd)) {
  718. element.className = (element.className + ' ' + classToAdd).trim();
  719. }
  720. return element;
  721. }
  722. /**
  723. * Remove a class name from an element.
  724. *
  725. * @param {Element} element
  726. * Element to remove a class name from.
  727. *
  728. * @param {string} classToRemove
  729. * Class name to remove
  730. *
  731. * @return {Element}
  732. * The DOM element with class name removed.
  733. */
  734. function removeClass(element, classToRemove) {
  735. if (element.classList) {
  736. element.classList.remove(classToRemove);
  737. } else {
  738. throwIfWhitespace(classToRemove);
  739. element.className = element.className.split(/\s+/).filter(function (c) {
  740. return c !== classToRemove;
  741. }).join(' ');
  742. }
  743. return element;
  744. }
  745. /**
  746. * The callback definition for toggleClass.
  747. *
  748. * @callback module:dom~PredicateCallback
  749. * @param {Element} element
  750. * The DOM element of the Component.
  751. *
  752. * @param {string} classToToggle
  753. * The `className` that wants to be toggled
  754. *
  755. * @return {boolean|undefined}
  756. * If `true` is returned, the `classToToggle` will be added to the
  757. * `element`. If `false`, the `classToToggle` will be removed from
  758. * the `element`. If `undefined`, the callback will be ignored.
  759. */
  760. /**
  761. * Adds or removes a class name to/from an element depending on an optional
  762. * condition or the presence/absence of the class name.
  763. *
  764. * @param {Element} element
  765. * The element to toggle a class name on.
  766. *
  767. * @param {string} classToToggle
  768. * The class that should be toggled.
  769. *
  770. * @param {boolean|module:dom~PredicateCallback} [predicate]
  771. * See the return value for {@link module:dom~PredicateCallback}
  772. *
  773. * @return {Element}
  774. * The element with a class that has been toggled.
  775. */
  776. function toggleClass(element, classToToggle, predicate) {
  777. // This CANNOT use `classList` internally because IE11 does not support the
  778. // second parameter to the `classList.toggle()` method! Which is fine because
  779. // `classList` will be used by the add/remove functions.
  780. var has = hasClass(element, classToToggle);
  781. if (typeof predicate === 'function') {
  782. predicate = predicate(element, classToToggle);
  783. }
  784. if (typeof predicate !== 'boolean') {
  785. predicate = !has;
  786. } // If the necessary class operation matches the current state of the
  787. // element, no action is required.
  788. if (predicate === has) {
  789. return;
  790. }
  791. if (predicate) {
  792. addClass(element, classToToggle);
  793. } else {
  794. removeClass(element, classToToggle);
  795. }
  796. return element;
  797. }
  798. /**
  799. * Apply attributes to an HTML element.
  800. *
  801. * @param {Element} el
  802. * Element to add attributes to.
  803. *
  804. * @param {Object} [attributes]
  805. * Attributes to be applied.
  806. */
  807. function setAttributes(el, attributes) {
  808. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  809. var attrValue = attributes[attrName];
  810. if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
  811. el.removeAttribute(attrName);
  812. } else {
  813. el.setAttribute(attrName, attrValue === true ? '' : attrValue);
  814. }
  815. });
  816. }
  817. /**
  818. * Get an element's attribute values, as defined on the HTML tag.
  819. *
  820. * Attributes are not the same as properties. They're defined on the tag
  821. * or with setAttribute.
  822. *
  823. * @param {Element} tag
  824. * Element from which to get tag attributes.
  825. *
  826. * @return {Object}
  827. * All attributes of the element. Boolean attributes will be `true` or
  828. * `false`, others will be strings.
  829. */
  830. function getAttributes(tag) {
  831. var obj = {}; // known boolean attributes
  832. // we can check for matching boolean properties, but not all browsers
  833. // and not all tags know about these attributes, so, we still want to check them manually
  834. var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
  835. if (tag && tag.attributes && tag.attributes.length > 0) {
  836. var attrs = tag.attributes;
  837. for (var i = attrs.length - 1; i >= 0; i--) {
  838. var attrName = attrs[i].name;
  839. var attrVal = attrs[i].value; // check for known booleans
  840. // the matching element property will return a value for typeof
  841. if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
  842. // the value of an included boolean attribute is typically an empty
  843. // string ('') which would equal false if we just check for a false value.
  844. // we also don't want support bad code like autoplay='false'
  845. attrVal = attrVal !== null ? true : false;
  846. }
  847. obj[attrName] = attrVal;
  848. }
  849. }
  850. return obj;
  851. }
  852. /**
  853. * Get the value of an element's attribute.
  854. *
  855. * @param {Element} el
  856. * A DOM element.
  857. *
  858. * @param {string} attribute
  859. * Attribute to get the value of.
  860. *
  861. * @return {string}
  862. * The value of the attribute.
  863. */
  864. function getAttribute(el, attribute) {
  865. return el.getAttribute(attribute);
  866. }
  867. /**
  868. * Set the value of an element's attribute.
  869. *
  870. * @param {Element} el
  871. * A DOM element.
  872. *
  873. * @param {string} attribute
  874. * Attribute to set.
  875. *
  876. * @param {string} value
  877. * Value to set the attribute to.
  878. */
  879. function setAttribute(el, attribute, value) {
  880. el.setAttribute(attribute, value);
  881. }
  882. /**
  883. * Remove an element's attribute.
  884. *
  885. * @param {Element} el
  886. * A DOM element.
  887. *
  888. * @param {string} attribute
  889. * Attribute to remove.
  890. */
  891. function removeAttribute(el, attribute) {
  892. el.removeAttribute(attribute);
  893. }
  894. /**
  895. * Attempt to block the ability to select text.
  896. */
  897. function blockTextSelection() {
  898. document.body.focus();
  899. document.onselectstart = function () {
  900. return false;
  901. };
  902. }
  903. /**
  904. * Turn off text selection blocking.
  905. */
  906. function unblockTextSelection() {
  907. document.onselectstart = function () {
  908. return true;
  909. };
  910. }
  911. /**
  912. * Identical to the native `getBoundingClientRect` function, but ensures that
  913. * the method is supported at all (it is in all browsers we claim to support)
  914. * and that the element is in the DOM before continuing.
  915. *
  916. * This wrapper function also shims properties which are not provided by some
  917. * older browsers (namely, IE8).
  918. *
  919. * Additionally, some browsers do not support adding properties to a
  920. * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
  921. * properties (except `x` and `y` which are not widely supported). This helps
  922. * avoid implementations where keys are non-enumerable.
  923. *
  924. * @param {Element} el
  925. * Element whose `ClientRect` we want to calculate.
  926. *
  927. * @return {Object|undefined}
  928. * Always returns a plain object - or `undefined` if it cannot.
  929. */
  930. function getBoundingClientRect(el) {
  931. if (el && el.getBoundingClientRect && el.parentNode) {
  932. var rect = el.getBoundingClientRect();
  933. var result = {};
  934. ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
  935. if (rect[k] !== undefined) {
  936. result[k] = rect[k];
  937. }
  938. });
  939. if (!result.height) {
  940. result.height = parseFloat(computedStyle(el, 'height'));
  941. }
  942. if (!result.width) {
  943. result.width = parseFloat(computedStyle(el, 'width'));
  944. }
  945. return result;
  946. }
  947. }
  948. /**
  949. * Represents the position of a DOM element on the page.
  950. *
  951. * @typedef {Object} module:dom~Position
  952. *
  953. * @property {number} left
  954. * Pixels to the left.
  955. *
  956. * @property {number} top
  957. * Pixels from the top.
  958. */
  959. /**
  960. * Get the position of an element in the DOM.
  961. *
  962. * Uses `getBoundingClientRect` technique from John Resig.
  963. *
  964. * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
  965. *
  966. * @param {Element} el
  967. * Element from which to get offset.
  968. *
  969. * @return {module:dom~Position}
  970. * The position of the element that was passed in.
  971. */
  972. function findPosition(el) {
  973. var box;
  974. if (el.getBoundingClientRect && el.parentNode) {
  975. box = el.getBoundingClientRect();
  976. }
  977. if (!box) {
  978. return {
  979. left: 0,
  980. top: 0
  981. };
  982. }
  983. var docEl = document.documentElement;
  984. var body = document.body;
  985. var clientLeft = docEl.clientLeft || body.clientLeft || 0;
  986. var scrollLeft = window$1.pageXOffset || body.scrollLeft;
  987. var left = box.left + scrollLeft - clientLeft;
  988. var clientTop = docEl.clientTop || body.clientTop || 0;
  989. var scrollTop = window$1.pageYOffset || body.scrollTop;
  990. var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round
  991. return {
  992. left: Math.round(left),
  993. top: Math.round(top)
  994. };
  995. }
  996. /**
  997. * Represents x and y coordinates for a DOM element or mouse pointer.
  998. *
  999. * @typedef {Object} module:dom~Coordinates
  1000. *
  1001. * @property {number} x
  1002. * x coordinate in pixels
  1003. *
  1004. * @property {number} y
  1005. * y coordinate in pixels
  1006. */
  1007. /**
  1008. * Get the pointer position within an element.
  1009. *
  1010. * The base on the coordinates are the bottom left of the element.
  1011. *
  1012. * @param {Element} el
  1013. * Element on which to get the pointer position on.
  1014. *
  1015. * @param {EventTarget~Event} event
  1016. * Event object.
  1017. *
  1018. * @return {module:dom~Coordinates}
  1019. * A coordinates object corresponding to the mouse position.
  1020. *
  1021. */
  1022. function getPointerPosition(el, event) {
  1023. var position = {};
  1024. var box = findPosition(el);
  1025. var boxW = el.offsetWidth;
  1026. var boxH = el.offsetHeight;
  1027. var boxY = box.top;
  1028. var boxX = box.left;
  1029. var pageY = event.pageY;
  1030. var pageX = event.pageX;
  1031. if (event.changedTouches) {
  1032. pageX = event.changedTouches[0].pageX;
  1033. pageY = event.changedTouches[0].pageY;
  1034. }
  1035. position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
  1036. position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
  1037. return position;
  1038. }
  1039. /**
  1040. * Determines, via duck typing, whether or not a value is a text node.
  1041. *
  1042. * @param {Mixed} value
  1043. * Check if this value is a text node.
  1044. *
  1045. * @return {boolean}
  1046. * Will be `true` if the value is a text node, `false` otherwise.
  1047. */
  1048. function isTextNode(value) {
  1049. return isObject(value) && value.nodeType === 3;
  1050. }
  1051. /**
  1052. * Empties the contents of an element.
  1053. *
  1054. * @param {Element} el
  1055. * The element to empty children from
  1056. *
  1057. * @return {Element}
  1058. * The element with no children
  1059. */
  1060. function emptyEl(el) {
  1061. while (el.firstChild) {
  1062. el.removeChild(el.firstChild);
  1063. }
  1064. return el;
  1065. }
  1066. /**
  1067. * This is a mixed value that describes content to be injected into the DOM
  1068. * via some method. It can be of the following types:
  1069. *
  1070. * Type | Description
  1071. * -----------|-------------
  1072. * `string` | The value will be normalized into a text node.
  1073. * `Element` | The value will be accepted as-is.
  1074. * `TextNode` | The value will be accepted as-is.
  1075. * `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).
  1076. * `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.
  1077. *
  1078. * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
  1079. */
  1080. /**
  1081. * Normalizes content for eventual insertion into the DOM.
  1082. *
  1083. * This allows a wide range of content definition methods, but helps protect
  1084. * from falling into the trap of simply writing to `innerHTML`, which could
  1085. * be an XSS concern.
  1086. *
  1087. * The content for an element can be passed in multiple types and
  1088. * combinations, whose behavior is as follows:
  1089. *
  1090. * @param {module:dom~ContentDescriptor} content
  1091. * A content descriptor value.
  1092. *
  1093. * @return {Array}
  1094. * All of the content that was passed in, normalized to an array of
  1095. * elements or text nodes.
  1096. */
  1097. function normalizeContent(content) {
  1098. // First, invoke content if it is a function. If it produces an array,
  1099. // that needs to happen before normalization.
  1100. if (typeof content === 'function') {
  1101. content = content();
  1102. } // Next up, normalize to an array, so one or many items can be normalized,
  1103. // filtered, and returned.
  1104. return (Array.isArray(content) ? content : [content]).map(function (value) {
  1105. // First, invoke value if it is a function to produce a new value,
  1106. // which will be subsequently normalized to a Node of some kind.
  1107. if (typeof value === 'function') {
  1108. value = value();
  1109. }
  1110. if (isEl(value) || isTextNode(value)) {
  1111. return value;
  1112. }
  1113. if (typeof value === 'string' && /\S/.test(value)) {
  1114. return document.createTextNode(value);
  1115. }
  1116. }).filter(function (value) {
  1117. return value;
  1118. });
  1119. }
  1120. /**
  1121. * Normalizes and appends content to an element.
  1122. *
  1123. * @param {Element} el
  1124. * Element to append normalized content to.
  1125. *
  1126. * @param {module:dom~ContentDescriptor} content
  1127. * A content descriptor value.
  1128. *
  1129. * @return {Element}
  1130. * The element with appended normalized content.
  1131. */
  1132. function appendContent(el, content) {
  1133. normalizeContent(content).forEach(function (node) {
  1134. return el.appendChild(node);
  1135. });
  1136. return el;
  1137. }
  1138. /**
  1139. * Normalizes and inserts content into an element; this is identical to
  1140. * `appendContent()`, except it empties the element first.
  1141. *
  1142. * @param {Element} el
  1143. * Element to insert normalized content into.
  1144. *
  1145. * @param {module:dom~ContentDescriptor} content
  1146. * A content descriptor value.
  1147. *
  1148. * @return {Element}
  1149. * The element with inserted normalized content.
  1150. */
  1151. function insertContent(el, content) {
  1152. return appendContent(emptyEl(el), content);
  1153. }
  1154. /**
  1155. * Check if an event was a single left click.
  1156. *
  1157. * @param {EventTarget~Event} event
  1158. * Event object.
  1159. *
  1160. * @return {boolean}
  1161. * Will be `true` if a single left click, `false` otherwise.
  1162. */
  1163. function isSingleLeftClick(event) {
  1164. // Note: if you create something draggable, be sure to
  1165. // call it on both `mousedown` and `mousemove` event,
  1166. // otherwise `mousedown` should be enough for a button
  1167. if (event.button === undefined && event.buttons === undefined) {
  1168. // Why do we need `buttons` ?
  1169. // Because, middle mouse sometimes have this:
  1170. // e.button === 0 and e.buttons === 4
  1171. // Furthermore, we want to prevent combination click, something like
  1172. // HOLD middlemouse then left click, that would be
  1173. // e.button === 0, e.buttons === 5
  1174. // just `button` is not gonna work
  1175. // Alright, then what this block does ?
  1176. // this is for chrome `simulate mobile devices`
  1177. // I want to support this as well
  1178. return true;
  1179. }
  1180. if (event.button === 0 && event.buttons === undefined) {
  1181. // Touch screen, sometimes on some specific device, `buttons`
  1182. // doesn't have anything (safari on ios, blackberry...)
  1183. return true;
  1184. }
  1185. if (event.button !== 0 || event.buttons !== 1) {
  1186. // This is the reason we have those if else block above
  1187. // if any special case we can catch and let it slide
  1188. // we do it above, when get to here, this definitely
  1189. // is-not-left-click
  1190. return false;
  1191. }
  1192. return true;
  1193. }
  1194. /**
  1195. * Finds a single DOM element matching `selector` within the optional
  1196. * `context` of another DOM element (defaulting to `document`).
  1197. *
  1198. * @param {string} selector
  1199. * A valid CSS selector, which will be passed to `querySelector`.
  1200. *
  1201. * @param {Element|String} [context=document]
  1202. * A DOM element within which to query. Can also be a selector
  1203. * string in which case the first matching element will be used
  1204. * as context. If missing (or no element matches selector), falls
  1205. * back to `document`.
  1206. *
  1207. * @return {Element|null}
  1208. * The element that was found or null.
  1209. */
  1210. var $ = createQuerier('querySelector');
  1211. /**
  1212. * Finds a all DOM elements matching `selector` within the optional
  1213. * `context` of another DOM element (defaulting to `document`).
  1214. *
  1215. * @param {string} selector
  1216. * A valid CSS selector, which will be passed to `querySelectorAll`.
  1217. *
  1218. * @param {Element|String} [context=document]
  1219. * A DOM element within which to query. Can also be a selector
  1220. * string in which case the first matching element will be used
  1221. * as context. If missing (or no element matches selector), falls
  1222. * back to `document`.
  1223. *
  1224. * @return {NodeList}
  1225. * A element list of elements that were found. Will be empty if none
  1226. * were found.
  1227. *
  1228. */
  1229. var $$ = createQuerier('querySelectorAll');
  1230. var Dom = /*#__PURE__*/Object.freeze({
  1231. isReal: isReal,
  1232. isEl: isEl,
  1233. isInFrame: isInFrame,
  1234. createEl: createEl,
  1235. textContent: textContent,
  1236. prependTo: prependTo,
  1237. hasClass: hasClass,
  1238. addClass: addClass,
  1239. removeClass: removeClass,
  1240. toggleClass: toggleClass,
  1241. setAttributes: setAttributes,
  1242. getAttributes: getAttributes,
  1243. getAttribute: getAttribute,
  1244. setAttribute: setAttribute,
  1245. removeAttribute: removeAttribute,
  1246. blockTextSelection: blockTextSelection,
  1247. unblockTextSelection: unblockTextSelection,
  1248. getBoundingClientRect: getBoundingClientRect,
  1249. findPosition: findPosition,
  1250. getPointerPosition: getPointerPosition,
  1251. isTextNode: isTextNode,
  1252. emptyEl: emptyEl,
  1253. normalizeContent: normalizeContent,
  1254. appendContent: appendContent,
  1255. insertContent: insertContent,
  1256. isSingleLeftClick: isSingleLeftClick,
  1257. $: $,
  1258. $$: $$
  1259. });
  1260. /**
  1261. * @file guid.js
  1262. * @module guid
  1263. */
  1264. /**
  1265. * Unique ID for an element or function
  1266. * @type {Number}
  1267. */
  1268. var _guid = 1;
  1269. /**
  1270. * Get a unique auto-incrementing ID by number that has not been returned before.
  1271. *
  1272. * @return {number}
  1273. * A new unique ID.
  1274. */
  1275. function newGUID() {
  1276. return _guid++;
  1277. }
  1278. /**
  1279. * @file dom-data.js
  1280. * @module dom-data
  1281. */
  1282. /**
  1283. * Element Data Store.
  1284. *
  1285. * Allows for binding data to an element without putting it directly on the
  1286. * element. Ex. Event listeners are stored here.
  1287. * (also from jsninja.com, slightly modified and updated for closure compiler)
  1288. *
  1289. * @type {Object}
  1290. * @private
  1291. */
  1292. var elData = {};
  1293. /*
  1294. * Unique attribute name to store an element's guid in
  1295. *
  1296. * @type {String}
  1297. * @constant
  1298. * @private
  1299. */
  1300. var elIdAttr = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now());
  1301. /**
  1302. * Returns the cache object where data for an element is stored
  1303. *
  1304. * @param {Element} el
  1305. * Element to store data for.
  1306. *
  1307. * @return {Object}
  1308. * The cache object for that el that was passed in.
  1309. */
  1310. function getData(el) {
  1311. var id = el[elIdAttr];
  1312. if (!id) {
  1313. id = el[elIdAttr] = newGUID();
  1314. }
  1315. if (!elData[id]) {
  1316. elData[id] = {};
  1317. }
  1318. return elData[id];
  1319. }
  1320. /**
  1321. * Returns whether or not an element has cached data
  1322. *
  1323. * @param {Element} el
  1324. * Check if this element has cached data.
  1325. *
  1326. * @return {boolean}
  1327. * - True if the DOM element has cached data.
  1328. * - False otherwise.
  1329. */
  1330. function hasData(el) {
  1331. var id = el[elIdAttr];
  1332. if (!id) {
  1333. return false;
  1334. }
  1335. return !!Object.getOwnPropertyNames(elData[id]).length;
  1336. }
  1337. /**
  1338. * Delete data for the element from the cache and the guid attr from getElementById
  1339. *
  1340. * @param {Element} el
  1341. * Remove cached data for this element.
  1342. */
  1343. function removeData(el) {
  1344. var id = el[elIdAttr];
  1345. if (!id) {
  1346. return;
  1347. } // Remove all stored data
  1348. delete elData[id]; // Remove the elIdAttr property from the DOM node
  1349. try {
  1350. delete el[elIdAttr];
  1351. } catch (e) {
  1352. if (el.removeAttribute) {
  1353. el.removeAttribute(elIdAttr);
  1354. } else {
  1355. // IE doesn't appear to support removeAttribute on the document element
  1356. el[elIdAttr] = null;
  1357. }
  1358. }
  1359. }
  1360. /**
  1361. * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
  1362. * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
  1363. * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
  1364. * robust as jquery's, so there's probably some differences.
  1365. *
  1366. * @file events.js
  1367. * @module events
  1368. */
  1369. /**
  1370. * Clean up the listener cache and dispatchers
  1371. *
  1372. * @param {Element|Object} elem
  1373. * Element to clean up
  1374. *
  1375. * @param {string} type
  1376. * Type of event to clean up
  1377. */
  1378. function _cleanUpEvents(elem, type) {
  1379. var data = getData(elem); // Remove the events of a particular type if there are none left
  1380. if (data.handlers[type].length === 0) {
  1381. delete data.handlers[type]; // data.handlers[type] = null;
  1382. // Setting to null was causing an error with data.handlers
  1383. // Remove the meta-handler from the element
  1384. if (elem.removeEventListener) {
  1385. elem.removeEventListener(type, data.dispatcher, false);
  1386. } else if (elem.detachEvent) {
  1387. elem.detachEvent('on' + type, data.dispatcher);
  1388. }
  1389. } // Remove the events object if there are no types left
  1390. if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
  1391. delete data.handlers;
  1392. delete data.dispatcher;
  1393. delete data.disabled;
  1394. } // Finally remove the element data if there is no data left
  1395. if (Object.getOwnPropertyNames(data).length === 0) {
  1396. removeData(elem);
  1397. }
  1398. }
  1399. /**
  1400. * Loops through an array of event types and calls the requested method for each type.
  1401. *
  1402. * @param {Function} fn
  1403. * The event method we want to use.
  1404. *
  1405. * @param {Element|Object} elem
  1406. * Element or object to bind listeners to
  1407. *
  1408. * @param {string} type
  1409. * Type of event to bind to.
  1410. *
  1411. * @param {EventTarget~EventListener} callback
  1412. * Event listener.
  1413. */
  1414. function _handleMultipleEvents(fn, elem, types, callback) {
  1415. types.forEach(function (type) {
  1416. // Call the event method for each one of the types
  1417. fn(elem, type, callback);
  1418. });
  1419. }
  1420. /**
  1421. * Fix a native event to have standard property values
  1422. *
  1423. * @param {Object} event
  1424. * Event object to fix.
  1425. *
  1426. * @return {Object}
  1427. * Fixed event object.
  1428. */
  1429. function fixEvent(event) {
  1430. function returnTrue() {
  1431. return true;
  1432. }
  1433. function returnFalse() {
  1434. return false;
  1435. } // Test if fixing up is needed
  1436. // Used to check if !event.stopPropagation instead of isPropagationStopped
  1437. // But native events return true for stopPropagation, but don't have
  1438. // other expected methods like isPropagationStopped. Seems to be a problem
  1439. // with the Javascript Ninja code. So we're just overriding all events now.
  1440. if (!event || !event.isPropagationStopped) {
  1441. var old = event || window$1.event;
  1442. event = {}; // Clone the old object so that we can modify the values event = {};
  1443. // IE8 Doesn't like when you mess with native event properties
  1444. // Firefox returns false for event.hasOwnProperty('type') and other props
  1445. // which makes copying more difficult.
  1446. // TODO: Probably best to create a whitelist of event props
  1447. for (var key in old) {
  1448. // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
  1449. // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
  1450. // and webkitMovementX/Y
  1451. if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
  1452. // Chrome 32+ warns if you try to copy deprecated returnValue, but
  1453. // we still want to if preventDefault isn't supported (IE8).
  1454. if (!(key === 'returnValue' && old.preventDefault)) {
  1455. event[key] = old[key];
  1456. }
  1457. }
  1458. } // The event occurred on this element
  1459. if (!event.target) {
  1460. event.target = event.srcElement || document;
  1461. } // Handle which other element the event is related to
  1462. if (!event.relatedTarget) {
  1463. event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
  1464. } // Stop the default browser action
  1465. event.preventDefault = function () {
  1466. if (old.preventDefault) {
  1467. old.preventDefault();
  1468. }
  1469. event.returnValue = false;
  1470. old.returnValue = false;
  1471. event.defaultPrevented = true;
  1472. };
  1473. event.defaultPrevented = false; // Stop the event from bubbling
  1474. event.stopPropagation = function () {
  1475. if (old.stopPropagation) {
  1476. old.stopPropagation();
  1477. }
  1478. event.cancelBubble = true;
  1479. old.cancelBubble = true;
  1480. event.isPropagationStopped = returnTrue;
  1481. };
  1482. event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
  1483. event.stopImmediatePropagation = function () {
  1484. if (old.stopImmediatePropagation) {
  1485. old.stopImmediatePropagation();
  1486. }
  1487. event.isImmediatePropagationStopped = returnTrue;
  1488. event.stopPropagation();
  1489. };
  1490. event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
  1491. if (event.clientX !== null && event.clientX !== undefined) {
  1492. var doc = document.documentElement;
  1493. var body = document.body;
  1494. event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
  1495. event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
  1496. } // Handle key presses
  1497. event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
  1498. // 0 == left; 1 == middle; 2 == right
  1499. if (event.button !== null && event.button !== undefined) {
  1500. // The following is disabled because it does not pass videojs-standard
  1501. // and... yikes.
  1502. /* eslint-disable */
  1503. event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
  1504. /* eslint-enable */
  1505. }
  1506. } // Returns fixed-up instance
  1507. return event;
  1508. }
  1509. /**
  1510. * Whether passive event listeners are supported
  1511. */
  1512. var _supportsPassive = false;
  1513. (function () {
  1514. try {
  1515. var opts = Object.defineProperty({}, 'passive', {
  1516. get: function get() {
  1517. _supportsPassive = true;
  1518. }
  1519. });
  1520. window$1.addEventListener('test', null, opts);
  1521. window$1.removeEventListener('test', null, opts);
  1522. } catch (e) {// disregard
  1523. }
  1524. })();
  1525. /**
  1526. * Touch events Chrome expects to be passive
  1527. */
  1528. var passiveEvents = ['touchstart', 'touchmove'];
  1529. /**
  1530. * Add an event listener to element
  1531. * It stores the handler function in a separate cache object
  1532. * and adds a generic handler to the element's event,
  1533. * along with a unique id (guid) to the element.
  1534. *
  1535. * @param {Element|Object} elem
  1536. * Element or object to bind listeners to
  1537. *
  1538. * @param {string|string[]} type
  1539. * Type of event to bind to.
  1540. *
  1541. * @param {EventTarget~EventListener} fn
  1542. * Event listener.
  1543. */
  1544. function on(elem, type, fn) {
  1545. if (Array.isArray(type)) {
  1546. return _handleMultipleEvents(on, elem, type, fn);
  1547. }
  1548. var data = getData(elem); // We need a place to store all our handler data
  1549. if (!data.handlers) {
  1550. data.handlers = {};
  1551. }
  1552. if (!data.handlers[type]) {
  1553. data.handlers[type] = [];
  1554. }
  1555. if (!fn.guid) {
  1556. fn.guid = newGUID();
  1557. }
  1558. data.handlers[type].push(fn);
  1559. if (!data.dispatcher) {
  1560. data.disabled = false;
  1561. data.dispatcher = function (event, hash) {
  1562. if (data.disabled) {
  1563. return;
  1564. }
  1565. event = fixEvent(event);
  1566. var handlers = data.handlers[event.type];
  1567. if (handlers) {
  1568. // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
  1569. var handlersCopy = handlers.slice(0);
  1570. for (var m = 0, n = handlersCopy.length; m < n; m++) {
  1571. if (event.isImmediatePropagationStopped()) {
  1572. break;
  1573. } else {
  1574. try {
  1575. handlersCopy[m].call(elem, event, hash);
  1576. } catch (e) {
  1577. log.error(e);
  1578. }
  1579. }
  1580. }
  1581. }
  1582. };
  1583. }
  1584. if (data.handlers[type].length === 1) {
  1585. if (elem.addEventListener) {
  1586. var options = false;
  1587. if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
  1588. options = {
  1589. passive: true
  1590. };
  1591. }
  1592. elem.addEventListener(type, data.dispatcher, options);
  1593. } else if (elem.attachEvent) {
  1594. elem.attachEvent('on' + type, data.dispatcher);
  1595. }
  1596. }
  1597. }
  1598. /**
  1599. * Removes event listeners from an element
  1600. *
  1601. * @param {Element|Object} elem
  1602. * Object to remove listeners from.
  1603. *
  1604. * @param {string|string[]} [type]
  1605. * Type of listener to remove. Don't include to remove all events from element.
  1606. *
  1607. * @param {EventTarget~EventListener} [fn]
  1608. * Specific listener to remove. Don't include to remove listeners for an event
  1609. * type.
  1610. */
  1611. function off(elem, type, fn) {
  1612. // Don't want to add a cache object through getElData if not needed
  1613. if (!hasData(elem)) {
  1614. return;
  1615. }
  1616. var data = getData(elem); // If no events exist, nothing to unbind
  1617. if (!data.handlers) {
  1618. return;
  1619. }
  1620. if (Array.isArray(type)) {
  1621. return _handleMultipleEvents(off, elem, type, fn);
  1622. } // Utility function
  1623. var removeType = function removeType(el, t) {
  1624. data.handlers[t] = [];
  1625. _cleanUpEvents(el, t);
  1626. }; // Are we removing all bound events?
  1627. if (type === undefined) {
  1628. for (var t in data.handlers) {
  1629. if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
  1630. removeType(elem, t);
  1631. }
  1632. }
  1633. return;
  1634. }
  1635. var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
  1636. if (!handlers) {
  1637. return;
  1638. } // If no listener was provided, remove all listeners for type
  1639. if (!fn) {
  1640. removeType(elem, type);
  1641. return;
  1642. } // We're only removing a single handler
  1643. if (fn.guid) {
  1644. for (var n = 0; n < handlers.length; n++) {
  1645. if (handlers[n].guid === fn.guid) {
  1646. handlers.splice(n--, 1);
  1647. }
  1648. }
  1649. }
  1650. _cleanUpEvents(elem, type);
  1651. }
  1652. /**
  1653. * Trigger an event for an element
  1654. *
  1655. * @param {Element|Object} elem
  1656. * Element to trigger an event on
  1657. *
  1658. * @param {EventTarget~Event|string} event
  1659. * A string (the type) or an event object with a type attribute
  1660. *
  1661. * @param {Object} [hash]
  1662. * data hash to pass along with the event
  1663. *
  1664. * @return {boolean|undefined}
  1665. * Returns the opposite of `defaultPrevented` if default was
  1666. * prevented. Otherwise, returns `undefined`
  1667. */
  1668. function trigger(elem, event, hash) {
  1669. // Fetches element data and a reference to the parent (for bubbling).
  1670. // Don't want to add a data object to cache for every parent,
  1671. // so checking hasElData first.
  1672. var elemData = hasData(elem) ? getData(elem) : {};
  1673. var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
  1674. // handler;
  1675. // If an event name was passed as a string, creates an event out of it
  1676. if (typeof event === 'string') {
  1677. event = {
  1678. type: event,
  1679. target: elem
  1680. };
  1681. } else if (!event.target) {
  1682. event.target = elem;
  1683. } // Normalizes the event properties.
  1684. event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
  1685. if (elemData.dispatcher) {
  1686. elemData.dispatcher.call(elem, event, hash);
  1687. } // Unless explicitly stopped or the event does not bubble (e.g. media events)
  1688. // recursively calls this function to bubble the event up the DOM.
  1689. if (parent && !event.isPropagationStopped() && event.bubbles === true) {
  1690. trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
  1691. } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
  1692. var targetData = getData(event.target); // Checks if the target has a default action for this event.
  1693. if (event.target[event.type]) {
  1694. // Temporarily disables event dispatching on the target as we have already executed the handler.
  1695. targetData.disabled = true; // Executes the default action.
  1696. if (typeof event.target[event.type] === 'function') {
  1697. event.target[event.type]();
  1698. } // Re-enables event dispatching.
  1699. targetData.disabled = false;
  1700. }
  1701. } // Inform the triggerer if the default was prevented by returning false
  1702. return !event.defaultPrevented;
  1703. }
  1704. /**
  1705. * Trigger a listener only once for an event.
  1706. *
  1707. * @param {Element|Object} elem
  1708. * Element or object to bind to.
  1709. *
  1710. * @param {string|string[]} type
  1711. * Name/type of event
  1712. *
  1713. * @param {Event~EventListener} fn
  1714. * Event listener function
  1715. */
  1716. function one(elem, type, fn) {
  1717. if (Array.isArray(type)) {
  1718. return _handleMultipleEvents(one, elem, type, fn);
  1719. }
  1720. var func = function func() {
  1721. off(elem, type, func);
  1722. fn.apply(this, arguments);
  1723. }; // copy the guid to the new function so it can removed using the original function's ID
  1724. func.guid = fn.guid = fn.guid || newGUID();
  1725. on(elem, type, func);
  1726. }
  1727. var Events = /*#__PURE__*/Object.freeze({
  1728. fixEvent: fixEvent,
  1729. on: on,
  1730. off: off,
  1731. trigger: trigger,
  1732. one: one
  1733. });
  1734. /**
  1735. * @file setup.js - Functions for setting up a player without
  1736. * user interaction based on the data-setup `attribute` of the video tag.
  1737. *
  1738. * @module setup
  1739. */
  1740. var _windowLoaded = false;
  1741. var videojs;
  1742. /**
  1743. * Set up any tags that have a data-setup `attribute` when the player is started.
  1744. */
  1745. var autoSetup = function autoSetup() {
  1746. // Protect against breakage in non-browser environments and check global autoSetup option.
  1747. if (!isReal() || videojs.options.autoSetup === false) {
  1748. return;
  1749. }
  1750. var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
  1751. var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
  1752. var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
  1753. var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
  1754. if (mediaEls && mediaEls.length > 0) {
  1755. for (var i = 0, e = mediaEls.length; i < e; i++) {
  1756. var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
  1757. if (mediaEl && mediaEl.getAttribute) {
  1758. // Make sure this player hasn't already been set up.
  1759. if (mediaEl.player === undefined) {
  1760. var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
  1761. // We only auto-setup if they've added the data-setup attr.
  1762. if (options !== null) {
  1763. // Create new video.js instance.
  1764. videojs(mediaEl);
  1765. }
  1766. } // If getAttribute isn't defined, we need to wait for the DOM.
  1767. } else {
  1768. autoSetupTimeout(1);
  1769. break;
  1770. }
  1771. } // No videos were found, so keep looping unless page is finished loading.
  1772. } else if (!_windowLoaded) {
  1773. autoSetupTimeout(1);
  1774. }
  1775. };
  1776. /**
  1777. * Wait until the page is loaded before running autoSetup. This will be called in
  1778. * autoSetup if `hasLoaded` returns false.
  1779. *
  1780. * @param {number} wait
  1781. * How long to wait in ms
  1782. *
  1783. * @param {module:videojs} [vjs]
  1784. * The videojs library function
  1785. */
  1786. function autoSetupTimeout(wait, vjs) {
  1787. if (vjs) {
  1788. videojs = vjs;
  1789. }
  1790. window$1.setTimeout(autoSetup, wait);
  1791. }
  1792. if (isReal() && document.readyState === 'complete') {
  1793. _windowLoaded = true;
  1794. } else {
  1795. /**
  1796. * Listen for the load event on window, and set _windowLoaded to true.
  1797. *
  1798. * @listens load
  1799. */
  1800. one(window$1, 'load', function () {
  1801. _windowLoaded = true;
  1802. });
  1803. }
  1804. /**
  1805. * @file stylesheet.js
  1806. * @module stylesheet
  1807. */
  1808. /**
  1809. * Create a DOM syle element given a className for it.
  1810. *
  1811. * @param {string} className
  1812. * The className to add to the created style element.
  1813. *
  1814. * @return {Element}
  1815. * The element that was created.
  1816. */
  1817. var createStyleElement = function createStyleElement(className) {
  1818. var style = document.createElement('style');
  1819. style.className = className;
  1820. return style;
  1821. };
  1822. /**
  1823. * Add text to a DOM element.
  1824. *
  1825. * @param {Element} el
  1826. * The Element to add text content to.
  1827. *
  1828. * @param {string} content
  1829. * The text to add to the element.
  1830. */
  1831. var setTextContent = function setTextContent(el, content) {
  1832. if (el.styleSheet) {
  1833. el.styleSheet.cssText = content;
  1834. } else {
  1835. el.textContent = content;
  1836. }
  1837. };
  1838. /**
  1839. * @file fn.js
  1840. * @module fn
  1841. */
  1842. /**
  1843. * Bind (a.k.a proxy or context). A simple method for changing the context of
  1844. * a function.
  1845. *
  1846. * It also stores a unique id on the function so it can be easily removed from
  1847. * events.
  1848. *
  1849. * @function
  1850. * @param {Mixed} context
  1851. * The object to bind as scope.
  1852. *
  1853. * @param {Function} fn
  1854. * The function to be bound to a scope.
  1855. *
  1856. * @param {number} [uid]
  1857. * An optional unique ID for the function to be set
  1858. *
  1859. * @return {Function}
  1860. * The new function that will be bound into the context given
  1861. */
  1862. var bind = function bind(context, fn, uid) {
  1863. // Make sure the function has a unique ID
  1864. if (!fn.guid) {
  1865. fn.guid = newGUID();
  1866. } // Create the new function that changes the context
  1867. var bound = function bound() {
  1868. return fn.apply(context, arguments);
  1869. }; // Allow for the ability to individualize this function
  1870. // Needed in the case where multiple objects might share the same prototype
  1871. // IF both items add an event listener with the same function, then you try to remove just one
  1872. // it will remove both because they both have the same guid.
  1873. // when using this, you need to use the bind method when you remove the listener as well.
  1874. // currently used in text tracks
  1875. bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
  1876. return bound;
  1877. };
  1878. /**
  1879. * Wraps the given function, `fn`, with a new function that only invokes `fn`
  1880. * at most once per every `wait` milliseconds.
  1881. *
  1882. * @function
  1883. * @param {Function} fn
  1884. * The function to be throttled.
  1885. *
  1886. * @param {number} wait
  1887. * The number of milliseconds by which to throttle.
  1888. *
  1889. * @return {Function}
  1890. */
  1891. var throttle = function throttle(fn, wait) {
  1892. var last = window$1.performance.now();
  1893. var throttled = function throttled() {
  1894. var now = window$1.performance.now();
  1895. if (now - last >= wait) {
  1896. fn.apply(void 0, arguments);
  1897. last = now;
  1898. }
  1899. };
  1900. return throttled;
  1901. };
  1902. /**
  1903. * Creates a debounced function that delays invoking `func` until after `wait`
  1904. * milliseconds have elapsed since the last time the debounced function was
  1905. * invoked.
  1906. *
  1907. * Inspired by lodash and underscore implementations.
  1908. *
  1909. * @function
  1910. * @param {Function} func
  1911. * The function to wrap with debounce behavior.
  1912. *
  1913. * @param {number} wait
  1914. * The number of milliseconds to wait after the last invocation.
  1915. *
  1916. * @param {boolean} [immediate]
  1917. * Whether or not to invoke the function immediately upon creation.
  1918. *
  1919. * @param {Object} [context=window]
  1920. * The "context" in which the debounced function should debounce. For
  1921. * example, if this function should be tied to a Video.js player,
  1922. * the player can be passed here. Alternatively, defaults to the
  1923. * global `window` object.
  1924. *
  1925. * @return {Function}
  1926. * A debounced function.
  1927. */
  1928. var debounce = function debounce(func, wait, immediate, context) {
  1929. if (context === void 0) {
  1930. context = window$1;
  1931. }
  1932. var timeout;
  1933. var cancel = function cancel() {
  1934. context.clearTimeout(timeout);
  1935. timeout = null;
  1936. };
  1937. /* eslint-disable consistent-this */
  1938. var debounced = function debounced() {
  1939. var self = this;
  1940. var args = arguments;
  1941. var _later = function later() {
  1942. timeout = null;
  1943. _later = null;
  1944. if (!immediate) {
  1945. func.apply(self, args);
  1946. }
  1947. };
  1948. if (!timeout && immediate) {
  1949. func.apply(self, args);
  1950. }
  1951. context.clearTimeout(timeout);
  1952. timeout = context.setTimeout(_later, wait);
  1953. };
  1954. /* eslint-enable consistent-this */
  1955. debounced.cancel = cancel;
  1956. return debounced;
  1957. };
  1958. /**
  1959. * @file src/js/event-target.js
  1960. */
  1961. /**
  1962. * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
  1963. * adds shorthand functions that wrap around lengthy functions. For example:
  1964. * the `on` function is a wrapper around `addEventListener`.
  1965. *
  1966. * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
  1967. * @class EventTarget
  1968. */
  1969. var EventTarget = function EventTarget() {};
  1970. /**
  1971. * A Custom DOM event.
  1972. *
  1973. * @typedef {Object} EventTarget~Event
  1974. * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
  1975. */
  1976. /**
  1977. * All event listeners should follow the following format.
  1978. *
  1979. * @callback EventTarget~EventListener
  1980. * @this {EventTarget}
  1981. *
  1982. * @param {EventTarget~Event} event
  1983. * the event that triggered this function
  1984. *
  1985. * @param {Object} [hash]
  1986. * hash of data sent during the event
  1987. */
  1988. /**
  1989. * An object containing event names as keys and booleans as values.
  1990. *
  1991. * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
  1992. * will have extra functionality. See that function for more information.
  1993. *
  1994. * @property EventTarget.prototype.allowedEvents_
  1995. * @private
  1996. */
  1997. EventTarget.prototype.allowedEvents_ = {};
  1998. /**
  1999. * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
  2000. * function that will get called when an event with a certain name gets triggered.
  2001. *
  2002. * @param {string|string[]} type
  2003. * An event name or an array of event names.
  2004. *
  2005. * @param {EventTarget~EventListener} fn
  2006. * The function to call with `EventTarget`s
  2007. */
  2008. EventTarget.prototype.on = function (type, fn) {
  2009. // Remove the addEventListener alias before calling Events.on
  2010. // so we don't get into an infinite type loop
  2011. var ael = this.addEventListener;
  2012. this.addEventListener = function () {};
  2013. on(this, type, fn);
  2014. this.addEventListener = ael;
  2015. };
  2016. /**
  2017. * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
  2018. * the standard DOM API.
  2019. *
  2020. * @function
  2021. * @see {@link EventTarget#on}
  2022. */
  2023. EventTarget.prototype.addEventListener = EventTarget.prototype.on;
  2024. /**
  2025. * Removes an `event listener` for a specific event from an instance of `EventTarget`.
  2026. * This makes it so that the `event listener` will no longer get called when the
  2027. * named event happens.
  2028. *
  2029. * @param {string|string[]} type
  2030. * An event name or an array of event names.
  2031. *
  2032. * @param {EventTarget~EventListener} fn
  2033. * The function to remove.
  2034. */
  2035. EventTarget.prototype.off = function (type, fn) {
  2036. off(this, type, fn);
  2037. };
  2038. /**
  2039. * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
  2040. * the standard DOM API.
  2041. *
  2042. * @function
  2043. * @see {@link EventTarget#off}
  2044. */
  2045. EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
  2046. /**
  2047. * This function will add an `event listener` that gets triggered only once. After the
  2048. * first trigger it will get removed. This is like adding an `event listener`
  2049. * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
  2050. *
  2051. * @param {string|string[]} type
  2052. * An event name or an array of event names.
  2053. *
  2054. * @param {EventTarget~EventListener} fn
  2055. * The function to be called once for each event name.
  2056. */
  2057. EventTarget.prototype.one = function (type, fn) {
  2058. // Remove the addEventListener alialing Events.on
  2059. // so we don't get into an infinite type loop
  2060. var ael = this.addEventListener;
  2061. this.addEventListener = function () {};
  2062. one(this, type, fn);
  2063. this.addEventListener = ael;
  2064. };
  2065. /**
  2066. * This function causes an event to happen. This will then cause any `event listeners`
  2067. * that are waiting for that event, to get called. If there are no `event listeners`
  2068. * for an event then nothing will happen.
  2069. *
  2070. * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
  2071. * Trigger will also call the `on` + `uppercaseEventName` function.
  2072. *
  2073. * Example:
  2074. * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
  2075. * `onClick` if it exists.
  2076. *
  2077. * @param {string|EventTarget~Event|Object} event
  2078. * The name of the event, an `Event`, or an object with a key of type set to
  2079. * an event name.
  2080. */
  2081. EventTarget.prototype.trigger = function (event) {
  2082. var type = event.type || event; // deprecation
  2083. // In a future version we should default target to `this`
  2084. // similar to how we default the target to `elem` in
  2085. // `Events.trigger`. Right now the default `target` will be
  2086. // `document` due to the `Event.fixEvent` call.
  2087. if (typeof event === 'string') {
  2088. event = {
  2089. type: type
  2090. };
  2091. }
  2092. event = fixEvent(event);
  2093. if (this.allowedEvents_[type] && this['on' + type]) {
  2094. this['on' + type](event);
  2095. }
  2096. trigger(this, event);
  2097. };
  2098. /**
  2099. * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
  2100. * the standard DOM API.
  2101. *
  2102. * @function
  2103. * @see {@link EventTarget#trigger}
  2104. */
  2105. EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
  2106. var EVENT_MAP;
  2107. EventTarget.prototype.queueTrigger = function (event) {
  2108. var _this = this;
  2109. // only set up EVENT_MAP if it'll be used
  2110. if (!EVENT_MAP) {
  2111. EVENT_MAP = new Map();
  2112. }
  2113. var type = event.type || event;
  2114. var map = EVENT_MAP.get(this);
  2115. if (!map) {
  2116. map = new Map();
  2117. EVENT_MAP.set(this, map);
  2118. }
  2119. var oldTimeout = map.get(type);
  2120. map.delete(type);
  2121. window$1.clearTimeout(oldTimeout);
  2122. var timeout = window$1.setTimeout(function () {
  2123. // if we cleared out all timeouts for the current target, delete its map
  2124. if (map.size === 0) {
  2125. map = null;
  2126. EVENT_MAP.delete(_this);
  2127. }
  2128. _this.trigger(event);
  2129. }, 0);
  2130. map.set(type, timeout);
  2131. };
  2132. /**
  2133. * @file mixins/evented.js
  2134. * @module evented
  2135. */
  2136. /**
  2137. * Returns whether or not an object has had the evented mixin applied.
  2138. *
  2139. * @param {Object} object
  2140. * An object to test.
  2141. *
  2142. * @return {boolean}
  2143. * Whether or not the object appears to be evented.
  2144. */
  2145. var isEvented = function isEvented(object) {
  2146. return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
  2147. return typeof object[k] === 'function';
  2148. });
  2149. };
  2150. /**
  2151. * Adds a callback to run after the evented mixin applied.
  2152. *
  2153. * @param {Object} object
  2154. * An object to Add
  2155. * @param {Function} callback
  2156. * The callback to run.
  2157. */
  2158. var addEventedCallback = function addEventedCallback(target, callback) {
  2159. if (isEvented(target)) {
  2160. callback();
  2161. } else {
  2162. if (!target.eventedCallbacks) {
  2163. target.eventedCallbacks = [];
  2164. }
  2165. target.eventedCallbacks.push(callback);
  2166. }
  2167. };
  2168. /**
  2169. * Whether a value is a valid event type - non-empty string or array.
  2170. *
  2171. * @private
  2172. * @param {string|Array} type
  2173. * The type value to test.
  2174. *
  2175. * @return {boolean}
  2176. * Whether or not the type is a valid event type.
  2177. */
  2178. var isValidEventType = function isValidEventType(type) {
  2179. return (// The regex here verifies that the `type` contains at least one non-
  2180. // whitespace character.
  2181. typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
  2182. );
  2183. };
  2184. /**
  2185. * Validates a value to determine if it is a valid event target. Throws if not.
  2186. *
  2187. * @private
  2188. * @throws {Error}
  2189. * If the target does not appear to be a valid event target.
  2190. *
  2191. * @param {Object} target
  2192. * The object to test.
  2193. */
  2194. var validateTarget = function validateTarget(target) {
  2195. if (!target.nodeName && !isEvented(target)) {
  2196. throw new Error('Invalid target; must be a DOM node or evented object.');
  2197. }
  2198. };
  2199. /**
  2200. * Validates a value to determine if it is a valid event target. Throws if not.
  2201. *
  2202. * @private
  2203. * @throws {Error}
  2204. * If the type does not appear to be a valid event type.
  2205. *
  2206. * @param {string|Array} type
  2207. * The type to test.
  2208. */
  2209. var validateEventType = function validateEventType(type) {
  2210. if (!isValidEventType(type)) {
  2211. throw new Error('Invalid event type; must be a non-empty string or array.');
  2212. }
  2213. };
  2214. /**
  2215. * Validates a value to determine if it is a valid listener. Throws if not.
  2216. *
  2217. * @private
  2218. * @throws {Error}
  2219. * If the listener is not a function.
  2220. *
  2221. * @param {Function} listener
  2222. * The listener to test.
  2223. */
  2224. var validateListener = function validateListener(listener) {
  2225. if (typeof listener !== 'function') {
  2226. throw new Error('Invalid listener; must be a function.');
  2227. }
  2228. };
  2229. /**
  2230. * Takes an array of arguments given to `on()` or `one()`, validates them, and
  2231. * normalizes them into an object.
  2232. *
  2233. * @private
  2234. * @param {Object} self
  2235. * The evented object on which `on()` or `one()` was called. This
  2236. * object will be bound as the `this` value for the listener.
  2237. *
  2238. * @param {Array} args
  2239. * An array of arguments passed to `on()` or `one()`.
  2240. *
  2241. * @return {Object}
  2242. * An object containing useful values for `on()` or `one()` calls.
  2243. */
  2244. var normalizeListenArgs = function normalizeListenArgs(self, args) {
  2245. // If the number of arguments is less than 3, the target is always the
  2246. // evented object itself.
  2247. var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
  2248. var target;
  2249. var type;
  2250. var listener;
  2251. if (isTargetingSelf) {
  2252. target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
  2253. // the evented object itself.
  2254. if (args.length >= 3) {
  2255. args.shift();
  2256. }
  2257. type = args[0];
  2258. listener = args[1];
  2259. } else {
  2260. target = args[0];
  2261. type = args[1];
  2262. listener = args[2];
  2263. }
  2264. validateTarget(target);
  2265. validateEventType(type);
  2266. validateListener(listener);
  2267. listener = bind(self, listener);
  2268. return {
  2269. isTargetingSelf: isTargetingSelf,
  2270. target: target,
  2271. type: type,
  2272. listener: listener
  2273. };
  2274. };
  2275. /**
  2276. * Adds the listener to the event type(s) on the target, normalizing for
  2277. * the type of target.
  2278. *
  2279. * @private
  2280. * @param {Element|Object} target
  2281. * A DOM node or evented object.
  2282. *
  2283. * @param {string} method
  2284. * The event binding method to use ("on" or "one").
  2285. *
  2286. * @param {string|Array} type
  2287. * One or more event type(s).
  2288. *
  2289. * @param {Function} listener
  2290. * A listener function.
  2291. */
  2292. var listen = function listen(target, method, type, listener) {
  2293. validateTarget(target);
  2294. if (target.nodeName) {
  2295. Events[method](target, type, listener);
  2296. } else {
  2297. target[method](type, listener);
  2298. }
  2299. };
  2300. /**
  2301. * Contains methods that provide event capabilities to an object which is passed
  2302. * to {@link module:evented|evented}.
  2303. *
  2304. * @mixin EventedMixin
  2305. */
  2306. var EventedMixin = {
  2307. /**
  2308. * Add a listener to an event (or events) on this object or another evented
  2309. * object.
  2310. *
  2311. * @param {string|Array|Element|Object} targetOrType
  2312. * If this is a string or array, it represents the event type(s)
  2313. * that will trigger the listener.
  2314. *
  2315. * Another evented object can be passed here instead, which will
  2316. * cause the listener to listen for events on _that_ object.
  2317. *
  2318. * In either case, the listener's `this` value will be bound to
  2319. * this object.
  2320. *
  2321. * @param {string|Array|Function} typeOrListener
  2322. * If the first argument was a string or array, this should be the
  2323. * listener function. Otherwise, this is a string or array of event
  2324. * type(s).
  2325. *
  2326. * @param {Function} [listener]
  2327. * If the first argument was another evented object, this will be
  2328. * the listener function.
  2329. */
  2330. on: function on$$1() {
  2331. var _this = this;
  2332. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  2333. args[_key] = arguments[_key];
  2334. }
  2335. var _normalizeListenArgs = normalizeListenArgs(this, args),
  2336. isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
  2337. target = _normalizeListenArgs.target,
  2338. type = _normalizeListenArgs.type,
  2339. listener = _normalizeListenArgs.listener;
  2340. listen(target, 'on', type, listener); // If this object is listening to another evented object.
  2341. if (!isTargetingSelf) {
  2342. // If this object is disposed, remove the listener.
  2343. var removeListenerOnDispose = function removeListenerOnDispose() {
  2344. return _this.off(target, type, listener);
  2345. }; // Use the same function ID as the listener so we can remove it later it
  2346. // using the ID of the original listener.
  2347. removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
  2348. // that if the target is disposed BEFORE this object, we remove the
  2349. // removal listener that was just added. Otherwise, we create a memory leak.
  2350. var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
  2351. return _this.off('dispose', removeListenerOnDispose);
  2352. }; // Use the same function ID as the listener so we can remove it later
  2353. // it using the ID of the original listener.
  2354. removeRemoverOnTargetDispose.guid = listener.guid;
  2355. listen(this, 'on', 'dispose', removeListenerOnDispose);
  2356. listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
  2357. }
  2358. },
  2359. /**
  2360. * Add a listener to an event (or events) on this object or another evented
  2361. * object. The listener will only be called once and then removed.
  2362. *
  2363. * @param {string|Array|Element|Object} targetOrType
  2364. * If this is a string or array, it represents the event type(s)
  2365. * that will trigger the listener.
  2366. *
  2367. * Another evented object can be passed here instead, which will
  2368. * cause the listener to listen for events on _that_ object.
  2369. *
  2370. * In either case, the listener's `this` value will be bound to
  2371. * this object.
  2372. *
  2373. * @param {string|Array|Function} typeOrListener
  2374. * If the first argument was a string or array, this should be the
  2375. * listener function. Otherwise, this is a string or array of event
  2376. * type(s).
  2377. *
  2378. * @param {Function} [listener]
  2379. * If the first argument was another evented object, this will be
  2380. * the listener function.
  2381. */
  2382. one: function one$$1() {
  2383. var _this2 = this;
  2384. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  2385. args[_key2] = arguments[_key2];
  2386. }
  2387. var _normalizeListenArgs2 = normalizeListenArgs(this, args),
  2388. isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
  2389. target = _normalizeListenArgs2.target,
  2390. type = _normalizeListenArgs2.type,
  2391. listener = _normalizeListenArgs2.listener; // Targeting this evented object.
  2392. if (isTargetingSelf) {
  2393. listen(target, 'one', type, listener); // Targeting another evented object.
  2394. } else {
  2395. var wrapper = function wrapper() {
  2396. _this2.off(target, type, wrapper);
  2397. for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  2398. largs[_key3] = arguments[_key3];
  2399. }
  2400. listener.apply(null, largs);
  2401. }; // Use the same function ID as the listener so we can remove it later
  2402. // it using the ID of the original listener.
  2403. wrapper.guid = listener.guid;
  2404. listen(target, 'one', type, wrapper);
  2405. }
  2406. },
  2407. /**
  2408. * Removes listener(s) from event(s) on an evented object.
  2409. *
  2410. * @param {string|Array|Element|Object} [targetOrType]
  2411. * If this is a string or array, it represents the event type(s).
  2412. *
  2413. * Another evented object can be passed here instead, in which case
  2414. * ALL 3 arguments are _required_.
  2415. *
  2416. * @param {string|Array|Function} [typeOrListener]
  2417. * If the first argument was a string or array, this may be the
  2418. * listener function. Otherwise, this is a string or array of event
  2419. * type(s).
  2420. *
  2421. * @param {Function} [listener]
  2422. * If the first argument was another evented object, this will be
  2423. * the listener function; otherwise, _all_ listeners bound to the
  2424. * event type(s) will be removed.
  2425. */
  2426. off: function off$$1(targetOrType, typeOrListener, listener) {
  2427. // Targeting this evented object.
  2428. if (!targetOrType || isValidEventType(targetOrType)) {
  2429. off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
  2430. } else {
  2431. var target = targetOrType;
  2432. var type = typeOrListener; // Fail fast and in a meaningful way!
  2433. validateTarget(target);
  2434. validateEventType(type);
  2435. validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used
  2436. listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
  2437. // the same guid as the event listener in on().
  2438. this.off('dispose', listener);
  2439. if (target.nodeName) {
  2440. off(target, type, listener);
  2441. off(target, 'dispose', listener);
  2442. } else if (isEvented(target)) {
  2443. target.off(type, listener);
  2444. target.off('dispose', listener);
  2445. }
  2446. }
  2447. },
  2448. /**
  2449. * Fire an event on this evented object, causing its listeners to be called.
  2450. *
  2451. * @param {string|Object} event
  2452. * An event type or an object with a type property.
  2453. *
  2454. * @param {Object} [hash]
  2455. * An additional object to pass along to listeners.
  2456. *
  2457. * @return {boolean}
  2458. * Whether or not the default behavior was prevented.
  2459. */
  2460. trigger: function trigger$$1(event, hash) {
  2461. return trigger(this.eventBusEl_, event, hash);
  2462. }
  2463. };
  2464. /**
  2465. * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
  2466. *
  2467. * @param {Object} target
  2468. * The object to which to add event methods.
  2469. *
  2470. * @param {Object} [options={}]
  2471. * Options for customizing the mixin behavior.
  2472. *
  2473. * @param {string} [options.eventBusKey]
  2474. * By default, adds a `eventBusEl_` DOM element to the target object,
  2475. * which is used as an event bus. If the target object already has a
  2476. * DOM element that should be used, pass its key here.
  2477. *
  2478. * @return {Object}
  2479. * The target object.
  2480. */
  2481. function evented(target, options) {
  2482. if (options === void 0) {
  2483. options = {};
  2484. }
  2485. var _options = options,
  2486. eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
  2487. if (eventBusKey) {
  2488. if (!target[eventBusKey].nodeName) {
  2489. throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
  2490. }
  2491. target.eventBusEl_ = target[eventBusKey];
  2492. } else {
  2493. target.eventBusEl_ = createEl('span', {
  2494. className: 'vjs-event-bus'
  2495. });
  2496. }
  2497. assign(target, EventedMixin);
  2498. if (target.eventedCallbacks) {
  2499. target.eventedCallbacks.forEach(function (callback) {
  2500. callback();
  2501. });
  2502. } // When any evented object is disposed, it removes all its listeners.
  2503. target.on('dispose', function () {
  2504. target.off();
  2505. window$1.setTimeout(function () {
  2506. target.eventBusEl_ = null;
  2507. }, 0);
  2508. });
  2509. return target;
  2510. }
  2511. /**
  2512. * @file mixins/stateful.js
  2513. * @module stateful
  2514. */
  2515. /**
  2516. * Contains methods that provide statefulness to an object which is passed
  2517. * to {@link module:stateful}.
  2518. *
  2519. * @mixin StatefulMixin
  2520. */
  2521. var StatefulMixin = {
  2522. /**
  2523. * A hash containing arbitrary keys and values representing the state of
  2524. * the object.
  2525. *
  2526. * @type {Object}
  2527. */
  2528. state: {},
  2529. /**
  2530. * Set the state of an object by mutating its
  2531. * {@link module:stateful~StatefulMixin.state|state} object in place.
  2532. *
  2533. * @fires module:stateful~StatefulMixin#statechanged
  2534. * @param {Object|Function} stateUpdates
  2535. * A new set of properties to shallow-merge into the plugin state.
  2536. * Can be a plain object or a function returning a plain object.
  2537. *
  2538. * @return {Object|undefined}
  2539. * An object containing changes that occurred. If no changes
  2540. * occurred, returns `undefined`.
  2541. */
  2542. setState: function setState(stateUpdates) {
  2543. var _this = this;
  2544. // Support providing the `stateUpdates` state as a function.
  2545. if (typeof stateUpdates === 'function') {
  2546. stateUpdates = stateUpdates();
  2547. }
  2548. var changes;
  2549. each(stateUpdates, function (value, key) {
  2550. // Record the change if the value is different from what's in the
  2551. // current state.
  2552. if (_this.state[key] !== value) {
  2553. changes = changes || {};
  2554. changes[key] = {
  2555. from: _this.state[key],
  2556. to: value
  2557. };
  2558. }
  2559. _this.state[key] = value;
  2560. }); // Only trigger "statechange" if there were changes AND we have a trigger
  2561. // function. This allows us to not require that the target object be an
  2562. // evented object.
  2563. if (changes && isEvented(this)) {
  2564. /**
  2565. * An event triggered on an object that is both
  2566. * {@link module:stateful|stateful} and {@link module:evented|evented}
  2567. * indicating that its state has changed.
  2568. *
  2569. * @event module:stateful~StatefulMixin#statechanged
  2570. * @type {Object}
  2571. * @property {Object} changes
  2572. * A hash containing the properties that were changed and
  2573. * the values they were changed `from` and `to`.
  2574. */
  2575. this.trigger({
  2576. changes: changes,
  2577. type: 'statechanged'
  2578. });
  2579. }
  2580. return changes;
  2581. }
  2582. };
  2583. /**
  2584. * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
  2585. * object.
  2586. *
  2587. * If the target object is {@link module:evented|evented} and has a
  2588. * `handleStateChanged` method, that method will be automatically bound to the
  2589. * `statechanged` event on itself.
  2590. *
  2591. * @param {Object} target
  2592. * The object to be made stateful.
  2593. *
  2594. * @param {Object} [defaultState]
  2595. * A default set of properties to populate the newly-stateful object's
  2596. * `state` property.
  2597. *
  2598. * @return {Object}
  2599. * Returns the `target`.
  2600. */
  2601. function stateful(target, defaultState) {
  2602. assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
  2603. // added in that step.
  2604. target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
  2605. if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
  2606. target.on('statechanged', target.handleStateChanged);
  2607. }
  2608. return target;
  2609. }
  2610. /**
  2611. * @file to-title-case.js
  2612. * @module to-title-case
  2613. */
  2614. /**
  2615. * Uppercase the first letter of a string.
  2616. *
  2617. * @param {string} string
  2618. * String to be uppercased
  2619. *
  2620. * @return {string}
  2621. * The string with an uppercased first letter
  2622. */
  2623. function toTitleCase(string) {
  2624. if (typeof string !== 'string') {
  2625. return string;
  2626. }
  2627. return string.charAt(0).toUpperCase() + string.slice(1);
  2628. }
  2629. /**
  2630. * Compares the TitleCase versions of the two strings for equality.
  2631. *
  2632. * @param {string} str1
  2633. * The first string to compare
  2634. *
  2635. * @param {string} str2
  2636. * The second string to compare
  2637. *
  2638. * @return {boolean}
  2639. * Whether the TitleCase versions of the strings are equal
  2640. */
  2641. function titleCaseEquals(str1, str2) {
  2642. return toTitleCase(str1) === toTitleCase(str2);
  2643. }
  2644. /**
  2645. * @file merge-options.js
  2646. * @module merge-options
  2647. */
  2648. /**
  2649. * Merge two objects recursively.
  2650. *
  2651. * Performs a deep merge like
  2652. * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
  2653. * plain objects (not arrays, elements, or anything else).
  2654. *
  2655. * Non-plain object values will be copied directly from the right-most
  2656. * argument.
  2657. *
  2658. * @static
  2659. * @param {Object[]} sources
  2660. * One or more objects to merge into a new object.
  2661. *
  2662. * @return {Object}
  2663. * A new object that is the merged result of all sources.
  2664. */
  2665. function mergeOptions() {
  2666. var result = {};
  2667. for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
  2668. sources[_key] = arguments[_key];
  2669. }
  2670. sources.forEach(function (source) {
  2671. if (!source) {
  2672. return;
  2673. }
  2674. each(source, function (value, key) {
  2675. if (!isPlain(value)) {
  2676. result[key] = value;
  2677. return;
  2678. }
  2679. if (!isPlain(result[key])) {
  2680. result[key] = {};
  2681. }
  2682. result[key] = mergeOptions(result[key], value);
  2683. });
  2684. });
  2685. return result;
  2686. }
  2687. /**
  2688. * Player Component - Base class for all UI objects
  2689. *
  2690. * @file component.js
  2691. */
  2692. /**
  2693. * Base class for all UI Components.
  2694. * Components are UI objects which represent both a javascript object and an element
  2695. * in the DOM. They can be children of other components, and can have
  2696. * children themselves.
  2697. *
  2698. * Components can also use methods from {@link EventTarget}
  2699. */
  2700. var Component =
  2701. /*#__PURE__*/
  2702. function () {
  2703. /**
  2704. * A callback that is called when a component is ready. Does not have any
  2705. * paramters and any callback value will be ignored.
  2706. *
  2707. * @callback Component~ReadyCallback
  2708. * @this Component
  2709. */
  2710. /**
  2711. * Creates an instance of this class.
  2712. *
  2713. * @param {Player} player
  2714. * The `Player` that this class should be attached to.
  2715. *
  2716. * @param {Object} [options]
  2717. * The key/value store of player options.
  2718. *
  2719. * @param {Object[]} [options.children]
  2720. * An array of children objects to intialize this component with. Children objects have
  2721. * a name property that will be used if more than one component of the same type needs to be
  2722. * added.
  2723. *
  2724. * @param {Component~ReadyCallback} [ready]
  2725. * Function that gets called when the `Component` is ready.
  2726. */
  2727. function Component(player, options, ready) {
  2728. // The component might be the player itself and we can't pass `this` to super
  2729. if (!player && this.play) {
  2730. this.player_ = player = this; // eslint-disable-line
  2731. } else {
  2732. this.player_ = player;
  2733. } // Hold the reference to the parent component via `addChild` method
  2734. this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
  2735. this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options
  2736. options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied
  2737. this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
  2738. if (!this.id_) {
  2739. // Don't require the player ID function in the case of mock players
  2740. var id = player && player.id && player.id() || 'no_player';
  2741. this.id_ = id + "_component_" + newGUID();
  2742. }
  2743. this.name_ = options.name || null; // Create element if one wasn't provided in options
  2744. if (options.el) {
  2745. this.el_ = options.el;
  2746. } else if (options.createEl !== false) {
  2747. this.el_ = this.createEl();
  2748. } // if evented is anything except false, we want to mixin in evented
  2749. if (options.evented !== false) {
  2750. // Make this an evented object and use `el_`, if available, as its event bus
  2751. evented(this, {
  2752. eventBusKey: this.el_ ? 'el_' : null
  2753. });
  2754. }
  2755. stateful(this, this.constructor.defaultState);
  2756. this.children_ = [];
  2757. this.childIndex_ = {};
  2758. this.childNameIndex_ = {}; // Add any child components in options
  2759. if (options.initChildren !== false) {
  2760. this.initChildren();
  2761. }
  2762. this.ready(ready); // Don't want to trigger ready here or it will before init is actually
  2763. // finished for all children that run this constructor
  2764. if (options.reportTouchActivity !== false) {
  2765. this.enableTouchActivity();
  2766. }
  2767. }
  2768. /**
  2769. * Dispose of the `Component` and all child components.
  2770. *
  2771. * @fires Component#dispose
  2772. */
  2773. var _proto = Component.prototype;
  2774. _proto.dispose = function dispose() {
  2775. /**
  2776. * Triggered when a `Component` is disposed.
  2777. *
  2778. * @event Component#dispose
  2779. * @type {EventTarget~Event}
  2780. *
  2781. * @property {boolean} [bubbles=false]
  2782. * set to false so that the close event does not
  2783. * bubble up
  2784. */
  2785. this.trigger({
  2786. type: 'dispose',
  2787. bubbles: false
  2788. }); // Dispose all children.
  2789. if (this.children_) {
  2790. for (var i = this.children_.length - 1; i >= 0; i--) {
  2791. if (this.children_[i].dispose) {
  2792. this.children_[i].dispose();
  2793. }
  2794. }
  2795. } // Delete child references
  2796. this.children_ = null;
  2797. this.childIndex_ = null;
  2798. this.childNameIndex_ = null;
  2799. this.parentComponent_ = null;
  2800. if (this.el_) {
  2801. // Remove element from DOM
  2802. if (this.el_.parentNode) {
  2803. this.el_.parentNode.removeChild(this.el_);
  2804. }
  2805. removeData(this.el_);
  2806. this.el_ = null;
  2807. } // remove reference to the player after disposing of the element
  2808. this.player_ = null;
  2809. }
  2810. /**
  2811. * Return the {@link Player} that the `Component` has attached to.
  2812. *
  2813. * @return {Player}
  2814. * The player that this `Component` has attached to.
  2815. */
  2816. ;
  2817. _proto.player = function player() {
  2818. return this.player_;
  2819. }
  2820. /**
  2821. * Deep merge of options objects with new options.
  2822. * > Note: When both `obj` and `options` contain properties whose values are objects.
  2823. * The two properties get merged using {@link module:mergeOptions}
  2824. *
  2825. * @param {Object} obj
  2826. * The object that contains new options.
  2827. *
  2828. * @return {Object}
  2829. * A new object of `this.options_` and `obj` merged together.
  2830. *
  2831. * @deprecated since version 5
  2832. */
  2833. ;
  2834. _proto.options = function options(obj) {
  2835. log.warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
  2836. if (!obj) {
  2837. return this.options_;
  2838. }
  2839. this.options_ = mergeOptions(this.options_, obj);
  2840. return this.options_;
  2841. }
  2842. /**
  2843. * Get the `Component`s DOM element
  2844. *
  2845. * @return {Element}
  2846. * The DOM element for this `Component`.
  2847. */
  2848. ;
  2849. _proto.el = function el() {
  2850. return this.el_;
  2851. }
  2852. /**
  2853. * Create the `Component`s DOM element.
  2854. *
  2855. * @param {string} [tagName]
  2856. * Element's DOM node type. e.g. 'div'
  2857. *
  2858. * @param {Object} [properties]
  2859. * An object of properties that should be set.
  2860. *
  2861. * @param {Object} [attributes]
  2862. * An object of attributes that should be set.
  2863. *
  2864. * @return {Element}
  2865. * The element that gets created.
  2866. */
  2867. ;
  2868. _proto.createEl = function createEl$$1(tagName, properties, attributes) {
  2869. return createEl(tagName, properties, attributes);
  2870. }
  2871. /**
  2872. * Localize a string given the string in english.
  2873. *
  2874. * If tokens are provided, it'll try and run a simple token replacement on the provided string.
  2875. * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
  2876. *
  2877. * If a `defaultValue` is provided, it'll use that over `string`,
  2878. * if a value isn't found in provided language files.
  2879. * This is useful if you want to have a descriptive key for token replacement
  2880. * but have a succinct localized string and not require `en.json` to be included.
  2881. *
  2882. * Currently, it is used for the progress bar timing.
  2883. * ```js
  2884. * {
  2885. * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
  2886. * }
  2887. * ```
  2888. * It is then used like so:
  2889. * ```js
  2890. * this.localize('progress bar timing: currentTime={1} duration{2}',
  2891. * [this.player_.currentTime(), this.player_.duration()],
  2892. * '{1} of {2}');
  2893. * ```
  2894. *
  2895. * Which outputs something like: `01:23 of 24:56`.
  2896. *
  2897. *
  2898. * @param {string} string
  2899. * The string to localize and the key to lookup in the language files.
  2900. * @param {string[]} [tokens]
  2901. * If the current item has token replacements, provide the tokens here.
  2902. * @param {string} [defaultValue]
  2903. * Defaults to `string`. Can be a default value to use for token replacement
  2904. * if the lookup key is needed to be separate.
  2905. *
  2906. * @return {string}
  2907. * The localized string or if no localization exists the english string.
  2908. */
  2909. ;
  2910. _proto.localize = function localize(string, tokens, defaultValue) {
  2911. if (defaultValue === void 0) {
  2912. defaultValue = string;
  2913. }
  2914. var code = this.player_.language && this.player_.language();
  2915. var languages = this.player_.languages && this.player_.languages();
  2916. var language = languages && languages[code];
  2917. var primaryCode = code && code.split('-')[0];
  2918. var primaryLang = languages && languages[primaryCode];
  2919. var localizedString = defaultValue;
  2920. if (language && language[string]) {
  2921. localizedString = language[string];
  2922. } else if (primaryLang && primaryLang[string]) {
  2923. localizedString = primaryLang[string];
  2924. }
  2925. if (tokens) {
  2926. localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
  2927. var value = tokens[index - 1];
  2928. var ret = value;
  2929. if (typeof value === 'undefined') {
  2930. ret = match;
  2931. }
  2932. return ret;
  2933. });
  2934. }
  2935. return localizedString;
  2936. }
  2937. /**
  2938. * Return the `Component`s DOM element. This is where children get inserted.
  2939. * This will usually be the the same as the element returned in {@link Component#el}.
  2940. *
  2941. * @return {Element}
  2942. * The content element for this `Component`.
  2943. */
  2944. ;
  2945. _proto.contentEl = function contentEl() {
  2946. return this.contentEl_ || this.el_;
  2947. }
  2948. /**
  2949. * Get this `Component`s ID
  2950. *
  2951. * @return {string}
  2952. * The id of this `Component`
  2953. */
  2954. ;
  2955. _proto.id = function id() {
  2956. return this.id_;
  2957. }
  2958. /**
  2959. * Get the `Component`s name. The name gets used to reference the `Component`
  2960. * and is set during registration.
  2961. *
  2962. * @return {string}
  2963. * The name of this `Component`.
  2964. */
  2965. ;
  2966. _proto.name = function name() {
  2967. return this.name_;
  2968. }
  2969. /**
  2970. * Get an array of all child components
  2971. *
  2972. * @return {Array}
  2973. * The children
  2974. */
  2975. ;
  2976. _proto.children = function children() {
  2977. return this.children_;
  2978. }
  2979. /**
  2980. * Returns the child `Component` with the given `id`.
  2981. *
  2982. * @param {string} id
  2983. * The id of the child `Component` to get.
  2984. *
  2985. * @return {Component|undefined}
  2986. * The child `Component` with the given `id` or undefined.
  2987. */
  2988. ;
  2989. _proto.getChildById = function getChildById(id) {
  2990. return this.childIndex_[id];
  2991. }
  2992. /**
  2993. * Returns the child `Component` with the given `name`.
  2994. *
  2995. * @param {string} name
  2996. * The name of the child `Component` to get.
  2997. *
  2998. * @return {Component|undefined}
  2999. * The child `Component` with the given `name` or undefined.
  3000. */
  3001. ;
  3002. _proto.getChild = function getChild(name) {
  3003. if (!name) {
  3004. return;
  3005. }
  3006. name = toTitleCase(name);
  3007. return this.childNameIndex_[name];
  3008. }
  3009. /**
  3010. * Add a child `Component` inside the current `Component`.
  3011. *
  3012. *
  3013. * @param {string|Component} child
  3014. * The name or instance of a child to add.
  3015. *
  3016. * @param {Object} [options={}]
  3017. * The key/value store of options that will get passed to children of
  3018. * the child.
  3019. *
  3020. * @param {number} [index=this.children_.length]
  3021. * The index to attempt to add a child into.
  3022. *
  3023. * @return {Component}
  3024. * The `Component` that gets added as a child. When using a string the
  3025. * `Component` will get created by this process.
  3026. */
  3027. ;
  3028. _proto.addChild = function addChild(child, options, index) {
  3029. if (options === void 0) {
  3030. options = {};
  3031. }
  3032. if (index === void 0) {
  3033. index = this.children_.length;
  3034. }
  3035. var component;
  3036. var componentName; // If child is a string, create component with options
  3037. if (typeof child === 'string') {
  3038. componentName = toTitleCase(child);
  3039. var componentClassName = options.componentClass || componentName; // Set name through options
  3040. options.name = componentName; // Create a new object & element for this controls set
  3041. // If there's no .player_, this is a player
  3042. var ComponentClass = Component.getComponent(componentClassName);
  3043. if (!ComponentClass) {
  3044. throw new Error("Component " + componentClassName + " does not exist");
  3045. } // data stored directly on the videojs object may be
  3046. // misidentified as a component to retain
  3047. // backwards-compatibility with 4.x. check to make sure the
  3048. // component class can be instantiated.
  3049. if (typeof ComponentClass !== 'function') {
  3050. return null;
  3051. }
  3052. component = new ComponentClass(this.player_ || this, options); // child is a component instance
  3053. } else {
  3054. component = child;
  3055. }
  3056. if (component.parentComponent_) {
  3057. component.parentComponent_.removeChild(component);
  3058. }
  3059. this.children_.splice(index, 0, component);
  3060. component.parentComponent_ = this;
  3061. if (typeof component.id === 'function') {
  3062. this.childIndex_[component.id()] = component;
  3063. } // If a name wasn't used to create the component, check if we can use the
  3064. // name function of the component
  3065. componentName = componentName || component.name && toTitleCase(component.name());
  3066. if (componentName) {
  3067. this.childNameIndex_[componentName] = component;
  3068. } // Add the UI object's element to the container div (box)
  3069. // Having an element is not required
  3070. if (typeof component.el === 'function' && component.el()) {
  3071. var childNodes = this.contentEl().children;
  3072. var refNode = childNodes[index] || null;
  3073. this.contentEl().insertBefore(component.el(), refNode);
  3074. } // Return so it can stored on parent object if desired.
  3075. return component;
  3076. }
  3077. /**
  3078. * Remove a child `Component` from this `Component`s list of children. Also removes
  3079. * the child `Component`s element from this `Component`s element.
  3080. *
  3081. * @param {Component} component
  3082. * The child `Component` to remove.
  3083. */
  3084. ;
  3085. _proto.removeChild = function removeChild(component) {
  3086. if (typeof component === 'string') {
  3087. component = this.getChild(component);
  3088. }
  3089. if (!component || !this.children_) {
  3090. return;
  3091. }
  3092. var childFound = false;
  3093. for (var i = this.children_.length - 1; i >= 0; i--) {
  3094. if (this.children_[i] === component) {
  3095. childFound = true;
  3096. this.children_.splice(i, 1);
  3097. break;
  3098. }
  3099. }
  3100. if (!childFound) {
  3101. return;
  3102. }
  3103. component.parentComponent_ = null;
  3104. this.childIndex_[component.id()] = null;
  3105. this.childNameIndex_[component.name()] = null;
  3106. var compEl = component.el();
  3107. if (compEl && compEl.parentNode === this.contentEl()) {
  3108. this.contentEl().removeChild(component.el());
  3109. }
  3110. }
  3111. /**
  3112. * Add and initialize default child `Component`s based upon options.
  3113. */
  3114. ;
  3115. _proto.initChildren = function initChildren() {
  3116. var _this = this;
  3117. var children = this.options_.children;
  3118. if (children) {
  3119. // `this` is `parent`
  3120. var parentOptions = this.options_;
  3121. var handleAdd = function handleAdd(child) {
  3122. var name = child.name;
  3123. var opts = child.opts; // Allow options for children to be set at the parent options
  3124. // e.g. videojs(id, { controlBar: false });
  3125. // instead of videojs(id, { children: { controlBar: false });
  3126. if (parentOptions[name] !== undefined) {
  3127. opts = parentOptions[name];
  3128. } // Allow for disabling default components
  3129. // e.g. options['children']['posterImage'] = false
  3130. if (opts === false) {
  3131. return;
  3132. } // Allow options to be passed as a simple boolean if no configuration
  3133. // is necessary.
  3134. if (opts === true) {
  3135. opts = {};
  3136. } // We also want to pass the original player options
  3137. // to each component as well so they don't need to
  3138. // reach back into the player for options later.
  3139. opts.playerOptions = _this.options_.playerOptions; // Create and add the child component.
  3140. // Add a direct reference to the child by name on the parent instance.
  3141. // If two of the same component are used, different names should be supplied
  3142. // for each
  3143. var newChild = _this.addChild(name, opts);
  3144. if (newChild) {
  3145. _this[name] = newChild;
  3146. }
  3147. }; // Allow for an array of children details to passed in the options
  3148. var workingChildren;
  3149. var Tech = Component.getComponent('Tech');
  3150. if (Array.isArray(children)) {
  3151. workingChildren = children;
  3152. } else {
  3153. workingChildren = Object.keys(children);
  3154. }
  3155. workingChildren // children that are in this.options_ but also in workingChildren would
  3156. // give us extra children we do not want. So, we want to filter them out.
  3157. .concat(Object.keys(this.options_).filter(function (child) {
  3158. return !workingChildren.some(function (wchild) {
  3159. if (typeof wchild === 'string') {
  3160. return child === wchild;
  3161. }
  3162. return child === wchild.name;
  3163. });
  3164. })).map(function (child) {
  3165. var name;
  3166. var opts;
  3167. if (typeof child === 'string') {
  3168. name = child;
  3169. opts = children[name] || _this.options_[name] || {};
  3170. } else {
  3171. name = child.name;
  3172. opts = child;
  3173. }
  3174. return {
  3175. name: name,
  3176. opts: opts
  3177. };
  3178. }).filter(function (child) {
  3179. // we have to make sure that child.name isn't in the techOrder since
  3180. // techs are registerd as Components but can't aren't compatible
  3181. // See https://github.com/videojs/video.js/issues/2772
  3182. var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
  3183. return c && !Tech.isTech(c);
  3184. }).forEach(handleAdd);
  3185. }
  3186. }
  3187. /**
  3188. * Builds the default DOM class name. Should be overriden by sub-components.
  3189. *
  3190. * @return {string}
  3191. * The DOM class name for this object.
  3192. *
  3193. * @abstract
  3194. */
  3195. ;
  3196. _proto.buildCSSClass = function buildCSSClass() {
  3197. // Child classes can include a function that does:
  3198. // return 'CLASS NAME' + this._super();
  3199. return '';
  3200. }
  3201. /**
  3202. * Bind a listener to the component's ready state.
  3203. * Different from event listeners in that if the ready event has already happened
  3204. * it will trigger the function immediately.
  3205. *
  3206. * @return {Component}
  3207. * Returns itself; method can be chained.
  3208. */
  3209. ;
  3210. _proto.ready = function ready(fn, sync) {
  3211. if (sync === void 0) {
  3212. sync = false;
  3213. }
  3214. if (!fn) {
  3215. return;
  3216. }
  3217. if (!this.isReady_) {
  3218. this.readyQueue_ = this.readyQueue_ || [];
  3219. this.readyQueue_.push(fn);
  3220. return;
  3221. }
  3222. if (sync) {
  3223. fn.call(this);
  3224. } else {
  3225. // Call the function asynchronously by default for consistency
  3226. this.setTimeout(fn, 1);
  3227. }
  3228. }
  3229. /**
  3230. * Trigger all the ready listeners for this `Component`.
  3231. *
  3232. * @fires Component#ready
  3233. */
  3234. ;
  3235. _proto.triggerReady = function triggerReady() {
  3236. this.isReady_ = true; // Ensure ready is triggered asynchronously
  3237. this.setTimeout(function () {
  3238. var readyQueue = this.readyQueue_; // Reset Ready Queue
  3239. this.readyQueue_ = [];
  3240. if (readyQueue && readyQueue.length > 0) {
  3241. readyQueue.forEach(function (fn) {
  3242. fn.call(this);
  3243. }, this);
  3244. } // Allow for using event listeners also
  3245. /**
  3246. * Triggered when a `Component` is ready.
  3247. *
  3248. * @event Component#ready
  3249. * @type {EventTarget~Event}
  3250. */
  3251. this.trigger('ready');
  3252. }, 1);
  3253. }
  3254. /**
  3255. * Find a single DOM element matching a `selector`. This can be within the `Component`s
  3256. * `contentEl()` or another custom context.
  3257. *
  3258. * @param {string} selector
  3259. * A valid CSS selector, which will be passed to `querySelector`.
  3260. *
  3261. * @param {Element|string} [context=this.contentEl()]
  3262. * A DOM element within which to query. Can also be a selector string in
  3263. * which case the first matching element will get used as context. If
  3264. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3265. * nothing it falls back to `document`.
  3266. *
  3267. * @return {Element|null}
  3268. * the dom element that was found, or null
  3269. *
  3270. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3271. */
  3272. ;
  3273. _proto.$ = function $$$1(selector, context) {
  3274. return $(selector, context || this.contentEl());
  3275. }
  3276. /**
  3277. * Finds all DOM element matching a `selector`. This can be within the `Component`s
  3278. * `contentEl()` or another custom context.
  3279. *
  3280. * @param {string} selector
  3281. * A valid CSS selector, which will be passed to `querySelectorAll`.
  3282. *
  3283. * @param {Element|string} [context=this.contentEl()]
  3284. * A DOM element within which to query. Can also be a selector string in
  3285. * which case the first matching element will get used as context. If
  3286. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3287. * nothing it falls back to `document`.
  3288. *
  3289. * @return {NodeList}
  3290. * a list of dom elements that were found
  3291. *
  3292. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3293. */
  3294. ;
  3295. _proto.$$ = function $$$$1(selector, context) {
  3296. return $$(selector, context || this.contentEl());
  3297. }
  3298. /**
  3299. * Check if a component's element has a CSS class name.
  3300. *
  3301. * @param {string} classToCheck
  3302. * CSS class name to check.
  3303. *
  3304. * @return {boolean}
  3305. * - True if the `Component` has the class.
  3306. * - False if the `Component` does not have the class`
  3307. */
  3308. ;
  3309. _proto.hasClass = function hasClass$$1(classToCheck) {
  3310. return hasClass(this.el_, classToCheck);
  3311. }
  3312. /**
  3313. * Add a CSS class name to the `Component`s element.
  3314. *
  3315. * @param {string} classToAdd
  3316. * CSS class name to add
  3317. */
  3318. ;
  3319. _proto.addClass = function addClass$$1(classToAdd) {
  3320. addClass(this.el_, classToAdd);
  3321. }
  3322. /**
  3323. * Remove a CSS class name from the `Component`s element.
  3324. *
  3325. * @param {string} classToRemove
  3326. * CSS class name to remove
  3327. */
  3328. ;
  3329. _proto.removeClass = function removeClass$$1(classToRemove) {
  3330. removeClass(this.el_, classToRemove);
  3331. }
  3332. /**
  3333. * Add or remove a CSS class name from the component's element.
  3334. * - `classToToggle` gets added when {@link Component#hasClass} would return false.
  3335. * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
  3336. *
  3337. * @param {string} classToToggle
  3338. * The class to add or remove based on (@link Component#hasClass}
  3339. *
  3340. * @param {boolean|Dom~predicate} [predicate]
  3341. * An {@link Dom~predicate} function or a boolean
  3342. */
  3343. ;
  3344. _proto.toggleClass = function toggleClass$$1(classToToggle, predicate) {
  3345. toggleClass(this.el_, classToToggle, predicate);
  3346. }
  3347. /**
  3348. * Show the `Component`s element if it is hidden by removing the
  3349. * 'vjs-hidden' class name from it.
  3350. */
  3351. ;
  3352. _proto.show = function show() {
  3353. this.removeClass('vjs-hidden');
  3354. }
  3355. /**
  3356. * Hide the `Component`s element if it is currently showing by adding the
  3357. * 'vjs-hidden` class name to it.
  3358. */
  3359. ;
  3360. _proto.hide = function hide() {
  3361. this.addClass('vjs-hidden');
  3362. }
  3363. /**
  3364. * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
  3365. * class name to it. Used during fadeIn/fadeOut.
  3366. *
  3367. * @private
  3368. */
  3369. ;
  3370. _proto.lockShowing = function lockShowing() {
  3371. this.addClass('vjs-lock-showing');
  3372. }
  3373. /**
  3374. * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
  3375. * class name from it. Used during fadeIn/fadeOut.
  3376. *
  3377. * @private
  3378. */
  3379. ;
  3380. _proto.unlockShowing = function unlockShowing() {
  3381. this.removeClass('vjs-lock-showing');
  3382. }
  3383. /**
  3384. * Get the value of an attribute on the `Component`s element.
  3385. *
  3386. * @param {string} attribute
  3387. * Name of the attribute to get the value from.
  3388. *
  3389. * @return {string|null}
  3390. * - The value of the attribute that was asked for.
  3391. * - Can be an empty string on some browsers if the attribute does not exist
  3392. * or has no value
  3393. * - Most browsers will return null if the attibute does not exist or has
  3394. * no value.
  3395. *
  3396. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
  3397. */
  3398. ;
  3399. _proto.getAttribute = function getAttribute$$1(attribute) {
  3400. return getAttribute(this.el_, attribute);
  3401. }
  3402. /**
  3403. * Set the value of an attribute on the `Component`'s element
  3404. *
  3405. * @param {string} attribute
  3406. * Name of the attribute to set.
  3407. *
  3408. * @param {string} value
  3409. * Value to set the attribute to.
  3410. *
  3411. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
  3412. */
  3413. ;
  3414. _proto.setAttribute = function setAttribute$$1(attribute, value) {
  3415. setAttribute(this.el_, attribute, value);
  3416. }
  3417. /**
  3418. * Remove an attribute from the `Component`s element.
  3419. *
  3420. * @param {string} attribute
  3421. * Name of the attribute to remove.
  3422. *
  3423. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
  3424. */
  3425. ;
  3426. _proto.removeAttribute = function removeAttribute$$1(attribute) {
  3427. removeAttribute(this.el_, attribute);
  3428. }
  3429. /**
  3430. * Get or set the width of the component based upon the CSS styles.
  3431. * See {@link Component#dimension} for more detailed information.
  3432. *
  3433. * @param {number|string} [num]
  3434. * The width that you want to set postfixed with '%', 'px' or nothing.
  3435. *
  3436. * @param {boolean} [skipListeners]
  3437. * Skip the componentresize event trigger
  3438. *
  3439. * @return {number|string}
  3440. * The width when getting, zero if there is no width. Can be a string
  3441. * postpixed with '%' or 'px'.
  3442. */
  3443. ;
  3444. _proto.width = function width(num, skipListeners) {
  3445. return this.dimension('width', num, skipListeners);
  3446. }
  3447. /**
  3448. * Get or set the height of the component based upon the CSS styles.
  3449. * See {@link Component#dimension} for more detailed information.
  3450. *
  3451. * @param {number|string} [num]
  3452. * The height that you want to set postfixed with '%', 'px' or nothing.
  3453. *
  3454. * @param {boolean} [skipListeners]
  3455. * Skip the componentresize event trigger
  3456. *
  3457. * @return {number|string}
  3458. * The width when getting, zero if there is no width. Can be a string
  3459. * postpixed with '%' or 'px'.
  3460. */
  3461. ;
  3462. _proto.height = function height(num, skipListeners) {
  3463. return this.dimension('height', num, skipListeners);
  3464. }
  3465. /**
  3466. * Set both the width and height of the `Component` element at the same time.
  3467. *
  3468. * @param {number|string} width
  3469. * Width to set the `Component`s element to.
  3470. *
  3471. * @param {number|string} height
  3472. * Height to set the `Component`s element to.
  3473. */
  3474. ;
  3475. _proto.dimensions = function dimensions(width, height) {
  3476. // Skip componentresize listeners on width for optimization
  3477. this.width(width, true);
  3478. this.height(height);
  3479. }
  3480. /**
  3481. * Get or set width or height of the `Component` element. This is the shared code
  3482. * for the {@link Component#width} and {@link Component#height}.
  3483. *
  3484. * Things to know:
  3485. * - If the width or height in an number this will return the number postfixed with 'px'.
  3486. * - If the width/height is a percent this will return the percent postfixed with '%'
  3487. * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
  3488. * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
  3489. * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
  3490. * for more information
  3491. * - If you want the computed style of the component, use {@link Component#currentWidth}
  3492. * and {@link {Component#currentHeight}
  3493. *
  3494. * @fires Component#componentresize
  3495. *
  3496. * @param {string} widthOrHeight
  3497. 8 'width' or 'height'
  3498. *
  3499. * @param {number|string} [num]
  3500. 8 New dimension
  3501. *
  3502. * @param {boolean} [skipListeners]
  3503. * Skip componentresize event trigger
  3504. *
  3505. * @return {number}
  3506. * The dimension when getting or 0 if unset
  3507. */
  3508. ;
  3509. _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
  3510. if (num !== undefined) {
  3511. // Set to zero if null or literally NaN (NaN !== NaN)
  3512. if (num === null || num !== num) {
  3513. num = 0;
  3514. } // Check if using css width/height (% or px) and adjust
  3515. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  3516. this.el_.style[widthOrHeight] = num;
  3517. } else if (num === 'auto') {
  3518. this.el_.style[widthOrHeight] = '';
  3519. } else {
  3520. this.el_.style[widthOrHeight] = num + 'px';
  3521. } // skipListeners allows us to avoid triggering the resize event when setting both width and height
  3522. if (!skipListeners) {
  3523. /**
  3524. * Triggered when a component is resized.
  3525. *
  3526. * @event Component#componentresize
  3527. * @type {EventTarget~Event}
  3528. */
  3529. this.trigger('componentresize');
  3530. }
  3531. return;
  3532. } // Not setting a value, so getting it
  3533. // Make sure element exists
  3534. if (!this.el_) {
  3535. return 0;
  3536. } // Get dimension value from style
  3537. var val = this.el_.style[widthOrHeight];
  3538. var pxIndex = val.indexOf('px');
  3539. if (pxIndex !== -1) {
  3540. // Return the pixel value with no 'px'
  3541. return parseInt(val.slice(0, pxIndex), 10);
  3542. } // No px so using % or no style was set, so falling back to offsetWidth/height
  3543. // If component has display:none, offset will return 0
  3544. // TODO: handle display:none and no dimension style using px
  3545. return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
  3546. }
  3547. /**
  3548. * Get the computed width or the height of the component's element.
  3549. *
  3550. * Uses `window.getComputedStyle`.
  3551. *
  3552. * @param {string} widthOrHeight
  3553. * A string containing 'width' or 'height'. Whichever one you want to get.
  3554. *
  3555. * @return {number}
  3556. * The dimension that gets asked for or 0 if nothing was set
  3557. * for that dimension.
  3558. */
  3559. ;
  3560. _proto.currentDimension = function currentDimension(widthOrHeight) {
  3561. var computedWidthOrHeight = 0;
  3562. if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
  3563. throw new Error('currentDimension only accepts width or height value');
  3564. }
  3565. if (typeof window$1.getComputedStyle === 'function') {
  3566. var computedStyle = window$1.getComputedStyle(this.el_);
  3567. computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
  3568. } // remove 'px' from variable and parse as integer
  3569. computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
  3570. // and we want to check the offset values.
  3571. // This code also runs wherever getComputedStyle doesn't exist.
  3572. if (computedWidthOrHeight === 0) {
  3573. var rule = "offset" + toTitleCase(widthOrHeight);
  3574. computedWidthOrHeight = this.el_[rule];
  3575. }
  3576. return computedWidthOrHeight;
  3577. }
  3578. /**
  3579. * An object that contains width and height values of the `Component`s
  3580. * computed style. Uses `window.getComputedStyle`.
  3581. *
  3582. * @typedef {Object} Component~DimensionObject
  3583. *
  3584. * @property {number} width
  3585. * The width of the `Component`s computed style.
  3586. *
  3587. * @property {number} height
  3588. * The height of the `Component`s computed style.
  3589. */
  3590. /**
  3591. * Get an object that contains computed width and height values of the
  3592. * component's element.
  3593. *
  3594. * Uses `window.getComputedStyle`.
  3595. *
  3596. * @return {Component~DimensionObject}
  3597. * The computed dimensions of the component's element.
  3598. */
  3599. ;
  3600. _proto.currentDimensions = function currentDimensions() {
  3601. return {
  3602. width: this.currentDimension('width'),
  3603. height: this.currentDimension('height')
  3604. };
  3605. }
  3606. /**
  3607. * Get the computed width of the component's element.
  3608. *
  3609. * Uses `window.getComputedStyle`.
  3610. *
  3611. * @return {number}
  3612. * The computed width of the component's element.
  3613. */
  3614. ;
  3615. _proto.currentWidth = function currentWidth() {
  3616. return this.currentDimension('width');
  3617. }
  3618. /**
  3619. * Get the computed height of the component's element.
  3620. *
  3621. * Uses `window.getComputedStyle`.
  3622. *
  3623. * @return {number}
  3624. * The computed height of the component's element.
  3625. */
  3626. ;
  3627. _proto.currentHeight = function currentHeight() {
  3628. return this.currentDimension('height');
  3629. }
  3630. /**
  3631. * Set the focus to this component
  3632. */
  3633. ;
  3634. _proto.focus = function focus() {
  3635. this.el_.focus();
  3636. }
  3637. /**
  3638. * Remove the focus from this component
  3639. */
  3640. ;
  3641. _proto.blur = function blur() {
  3642. this.el_.blur();
  3643. }
  3644. /**
  3645. * When this Component receives a keydown event which it does not process,
  3646. * it passes the event to the Player for handling.
  3647. *
  3648. * @param {EventTarget~Event} event
  3649. * The `keydown` event that caused this function to be called.
  3650. */
  3651. ;
  3652. _proto.handleKeyPress = function handleKeyPress(event) {
  3653. if (this.player_) {
  3654. this.player_.handleKeyPress(event);
  3655. }
  3656. }
  3657. /**
  3658. * Emit a 'tap' events when touch event support gets detected. This gets used to
  3659. * support toggling the controls through a tap on the video. They get enabled
  3660. * because every sub-component would have extra overhead otherwise.
  3661. *
  3662. * @private
  3663. * @fires Component#tap
  3664. * @listens Component#touchstart
  3665. * @listens Component#touchmove
  3666. * @listens Component#touchleave
  3667. * @listens Component#touchcancel
  3668. * @listens Component#touchend
  3669. */
  3670. ;
  3671. _proto.emitTapEvents = function emitTapEvents() {
  3672. // Track the start time so we can determine how long the touch lasted
  3673. var touchStart = 0;
  3674. var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
  3675. // Other popular libs use anywhere from 2 (hammer.js) to 15,
  3676. // so 10 seems like a nice, round number.
  3677. var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
  3678. var touchTimeThreshold = 200;
  3679. var couldBeTap;
  3680. this.on('touchstart', function (event) {
  3681. // If more than one finger, don't consider treating this as a click
  3682. if (event.touches.length === 1) {
  3683. // Copy pageX/pageY from the object
  3684. firstTouch = {
  3685. pageX: event.touches[0].pageX,
  3686. pageY: event.touches[0].pageY
  3687. }; // Record start time so we can detect a tap vs. "touch and hold"
  3688. touchStart = window$1.performance.now(); // Reset couldBeTap tracking
  3689. couldBeTap = true;
  3690. }
  3691. });
  3692. this.on('touchmove', function (event) {
  3693. // If more than one finger, don't consider treating this as a click
  3694. if (event.touches.length > 1) {
  3695. couldBeTap = false;
  3696. } else if (firstTouch) {
  3697. // Some devices will throw touchmoves for all but the slightest of taps.
  3698. // So, if we moved only a small distance, this could still be a tap
  3699. var xdiff = event.touches[0].pageX - firstTouch.pageX;
  3700. var ydiff = event.touches[0].pageY - firstTouch.pageY;
  3701. var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
  3702. if (touchDistance > tapMovementThreshold) {
  3703. couldBeTap = false;
  3704. }
  3705. }
  3706. });
  3707. var noTap = function noTap() {
  3708. couldBeTap = false;
  3709. }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  3710. this.on('touchleave', noTap);
  3711. this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
  3712. // event
  3713. this.on('touchend', function (event) {
  3714. firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
  3715. if (couldBeTap === true) {
  3716. // Measure how long the touch lasted
  3717. var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
  3718. if (touchTime < touchTimeThreshold) {
  3719. // Don't let browser turn this into a click
  3720. event.preventDefault();
  3721. /**
  3722. * Triggered when a `Component` is tapped.
  3723. *
  3724. * @event Component#tap
  3725. * @type {EventTarget~Event}
  3726. */
  3727. this.trigger('tap'); // It may be good to copy the touchend event object and change the
  3728. // type to tap, if the other event properties aren't exact after
  3729. // Events.fixEvent runs (e.g. event.target)
  3730. }
  3731. }
  3732. });
  3733. }
  3734. /**
  3735. * This function reports user activity whenever touch events happen. This can get
  3736. * turned off by any sub-components that wants touch events to act another way.
  3737. *
  3738. * Report user touch activity when touch events occur. User activity gets used to
  3739. * determine when controls should show/hide. It is simple when it comes to mouse
  3740. * events, because any mouse event should show the controls. So we capture mouse
  3741. * events that bubble up to the player and report activity when that happens.
  3742. * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
  3743. * controls. So touch events can't help us at the player level either.
  3744. *
  3745. * User activity gets checked asynchronously. So what could happen is a tap event
  3746. * on the video turns the controls off. Then the `touchend` event bubbles up to
  3747. * the player. Which, if it reported user activity, would turn the controls right
  3748. * back on. We also don't want to completely block touch events from bubbling up.
  3749. * Furthermore a `touchmove` event and anything other than a tap, should not turn
  3750. * controls back on.
  3751. *
  3752. * @listens Component#touchstart
  3753. * @listens Component#touchmove
  3754. * @listens Component#touchend
  3755. * @listens Component#touchcancel
  3756. */
  3757. ;
  3758. _proto.enableTouchActivity = function enableTouchActivity() {
  3759. // Don't continue if the root player doesn't support reporting user activity
  3760. if (!this.player() || !this.player().reportUserActivity) {
  3761. return;
  3762. } // listener for reporting that the user is active
  3763. var report = bind(this.player(), this.player().reportUserActivity);
  3764. var touchHolding;
  3765. this.on('touchstart', function () {
  3766. report(); // For as long as the they are touching the device or have their mouse down,
  3767. // we consider them active even if they're not moving their finger or mouse.
  3768. // So we want to continue to update that they are active
  3769. this.clearInterval(touchHolding); // report at the same interval as activityCheck
  3770. touchHolding = this.setInterval(report, 250);
  3771. });
  3772. var touchEnd = function touchEnd(event) {
  3773. report(); // stop the interval that maintains activity if the touch is holding
  3774. this.clearInterval(touchHolding);
  3775. };
  3776. this.on('touchmove', report);
  3777. this.on('touchend', touchEnd);
  3778. this.on('touchcancel', touchEnd);
  3779. }
  3780. /**
  3781. * A callback that has no parameters and is bound into `Component`s context.
  3782. *
  3783. * @callback Component~GenericCallback
  3784. * @this Component
  3785. */
  3786. /**
  3787. * Creates a function that runs after an `x` millisecond timeout. This function is a
  3788. * wrapper around `window.setTimeout`. There are a few reasons to use this one
  3789. * instead though:
  3790. * 1. It gets cleared via {@link Component#clearTimeout} when
  3791. * {@link Component#dispose} gets called.
  3792. * 2. The function callback will gets turned into a {@link Component~GenericCallback}
  3793. *
  3794. * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
  3795. * will cause its dispose listener not to get cleaned up! Please use
  3796. * {@link Component#clearTimeout} or {@link Component#dispose} instead.
  3797. *
  3798. * @param {Component~GenericCallback} fn
  3799. * The function that will be run after `timeout`.
  3800. *
  3801. * @param {number} timeout
  3802. * Timeout in milliseconds to delay before executing the specified function.
  3803. *
  3804. * @return {number}
  3805. * Returns a timeout ID that gets used to identify the timeout. It can also
  3806. * get used in {@link Component#clearTimeout} to clear the timeout that
  3807. * was set.
  3808. *
  3809. * @listens Component#dispose
  3810. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
  3811. */
  3812. ;
  3813. _proto.setTimeout = function setTimeout(fn, timeout) {
  3814. var _this2 = this;
  3815. // declare as variables so they are properly available in timeout function
  3816. // eslint-disable-next-line
  3817. var timeoutId, disposeFn;
  3818. fn = bind(this, fn);
  3819. timeoutId = window$1.setTimeout(function () {
  3820. _this2.off('dispose', disposeFn);
  3821. fn();
  3822. }, timeout);
  3823. disposeFn = function disposeFn() {
  3824. return _this2.clearTimeout(timeoutId);
  3825. };
  3826. disposeFn.guid = "vjs-timeout-" + timeoutId;
  3827. this.on('dispose', disposeFn);
  3828. return timeoutId;
  3829. }
  3830. /**
  3831. * Clears a timeout that gets created via `window.setTimeout` or
  3832. * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
  3833. * use this function instead of `window.clearTimout`. If you don't your dispose
  3834. * listener will not get cleaned up until {@link Component#dispose}!
  3835. *
  3836. * @param {number} timeoutId
  3837. * The id of the timeout to clear. The return value of
  3838. * {@link Component#setTimeout} or `window.setTimeout`.
  3839. *
  3840. * @return {number}
  3841. * Returns the timeout id that was cleared.
  3842. *
  3843. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
  3844. */
  3845. ;
  3846. _proto.clearTimeout = function clearTimeout(timeoutId) {
  3847. window$1.clearTimeout(timeoutId);
  3848. var disposeFn = function disposeFn() {};
  3849. disposeFn.guid = "vjs-timeout-" + timeoutId;
  3850. this.off('dispose', disposeFn);
  3851. return timeoutId;
  3852. }
  3853. /**
  3854. * Creates a function that gets run every `x` milliseconds. This function is a wrapper
  3855. * around `window.setInterval`. There are a few reasons to use this one instead though.
  3856. * 1. It gets cleared via {@link Component#clearInterval} when
  3857. * {@link Component#dispose} gets called.
  3858. * 2. The function callback will be a {@link Component~GenericCallback}
  3859. *
  3860. * @param {Component~GenericCallback} fn
  3861. * The function to run every `x` seconds.
  3862. *
  3863. * @param {number} interval
  3864. * Execute the specified function every `x` milliseconds.
  3865. *
  3866. * @return {number}
  3867. * Returns an id that can be used to identify the interval. It can also be be used in
  3868. * {@link Component#clearInterval} to clear the interval.
  3869. *
  3870. * @listens Component#dispose
  3871. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
  3872. */
  3873. ;
  3874. _proto.setInterval = function setInterval(fn, interval) {
  3875. var _this3 = this;
  3876. fn = bind(this, fn);
  3877. var intervalId = window$1.setInterval(fn, interval);
  3878. var disposeFn = function disposeFn() {
  3879. return _this3.clearInterval(intervalId);
  3880. };
  3881. disposeFn.guid = "vjs-interval-" + intervalId;
  3882. this.on('dispose', disposeFn);
  3883. return intervalId;
  3884. }
  3885. /**
  3886. * Clears an interval that gets created via `window.setInterval` or
  3887. * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
  3888. * use this function instead of `window.clearInterval`. If you don't your dispose
  3889. * listener will not get cleaned up until {@link Component#dispose}!
  3890. *
  3891. * @param {number} intervalId
  3892. * The id of the interval to clear. The return value of
  3893. * {@link Component#setInterval} or `window.setInterval`.
  3894. *
  3895. * @return {number}
  3896. * Returns the interval id that was cleared.
  3897. *
  3898. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
  3899. */
  3900. ;
  3901. _proto.clearInterval = function clearInterval(intervalId) {
  3902. window$1.clearInterval(intervalId);
  3903. var disposeFn = function disposeFn() {};
  3904. disposeFn.guid = "vjs-interval-" + intervalId;
  3905. this.off('dispose', disposeFn);
  3906. return intervalId;
  3907. }
  3908. /**
  3909. * Queues up a callback to be passed to requestAnimationFrame (rAF), but
  3910. * with a few extra bonuses:
  3911. *
  3912. * - Supports browsers that do not support rAF by falling back to
  3913. * {@link Component#setTimeout}.
  3914. *
  3915. * - The callback is turned into a {@link Component~GenericCallback} (i.e.
  3916. * bound to the component).
  3917. *
  3918. * - Automatic cancellation of the rAF callback is handled if the component
  3919. * is disposed before it is called.
  3920. *
  3921. * @param {Component~GenericCallback} fn
  3922. * A function that will be bound to this component and executed just
  3923. * before the browser's next repaint.
  3924. *
  3925. * @return {number}
  3926. * Returns an rAF ID that gets used to identify the timeout. It can
  3927. * also be used in {@link Component#cancelAnimationFrame} to cancel
  3928. * the animation frame callback.
  3929. *
  3930. * @listens Component#dispose
  3931. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
  3932. */
  3933. ;
  3934. _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
  3935. var _this4 = this;
  3936. // declare as variables so they are properly available in rAF function
  3937. // eslint-disable-next-line
  3938. var id, disposeFn;
  3939. if (this.supportsRaf_) {
  3940. fn = bind(this, fn);
  3941. id = window$1.requestAnimationFrame(function () {
  3942. _this4.off('dispose', disposeFn);
  3943. fn();
  3944. });
  3945. disposeFn = function disposeFn() {
  3946. return _this4.cancelAnimationFrame(id);
  3947. };
  3948. disposeFn.guid = "vjs-raf-" + id;
  3949. this.on('dispose', disposeFn);
  3950. return id;
  3951. } // Fall back to using a timer.
  3952. return this.setTimeout(fn, 1000 / 60);
  3953. }
  3954. /**
  3955. * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
  3956. * (rAF).
  3957. *
  3958. * If you queue an rAF callback via {@link Component#requestAnimationFrame},
  3959. * use this function instead of `window.cancelAnimationFrame`. If you don't,
  3960. * your dispose listener will not get cleaned up until {@link Component#dispose}!
  3961. *
  3962. * @param {number} id
  3963. * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
  3964. *
  3965. * @return {number}
  3966. * Returns the rAF ID that was cleared.
  3967. *
  3968. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
  3969. */
  3970. ;
  3971. _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
  3972. if (this.supportsRaf_) {
  3973. window$1.cancelAnimationFrame(id);
  3974. var disposeFn = function disposeFn() {};
  3975. disposeFn.guid = "vjs-raf-" + id;
  3976. this.off('dispose', disposeFn);
  3977. return id;
  3978. } // Fall back to using a timer.
  3979. return this.clearTimeout(id);
  3980. }
  3981. /**
  3982. * Register a `Component` with `videojs` given the name and the component.
  3983. *
  3984. * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
  3985. * should be registered using {@link Tech.registerTech} or
  3986. * {@link videojs:videojs.registerTech}.
  3987. *
  3988. * > NOTE: This function can also be seen on videojs as
  3989. * {@link videojs:videojs.registerComponent}.
  3990. *
  3991. * @param {string} name
  3992. * The name of the `Component` to register.
  3993. *
  3994. * @param {Component} ComponentToRegister
  3995. * The `Component` class to register.
  3996. *
  3997. * @return {Component}
  3998. * The `Component` that was registered.
  3999. */
  4000. ;
  4001. Component.registerComponent = function registerComponent(name, ComponentToRegister) {
  4002. if (typeof name !== 'string' || !name) {
  4003. throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
  4004. }
  4005. var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
  4006. var isTech = Tech && Tech.isTech(ComponentToRegister);
  4007. var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
  4008. if (isTech || !isComp) {
  4009. var reason;
  4010. if (isTech) {
  4011. reason = 'techs must be registered using Tech.registerTech()';
  4012. } else {
  4013. reason = 'must be a Component subclass';
  4014. }
  4015. throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
  4016. }
  4017. name = toTitleCase(name);
  4018. if (!Component.components_) {
  4019. Component.components_ = {};
  4020. }
  4021. var Player = Component.getComponent('Player');
  4022. if (name === 'Player' && Player && Player.players) {
  4023. var players = Player.players;
  4024. var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
  4025. // in Players.players. So, we must loop through and verify that the value
  4026. // for each item is not null. This allows registration of the Player component
  4027. // after all players have been disposed or before any were created.
  4028. if (players && playerNames.length > 0 && playerNames.map(function (pname) {
  4029. return players[pname];
  4030. }).every(Boolean)) {
  4031. throw new Error('Can not register Player component after player has been created.');
  4032. }
  4033. }
  4034. Component.components_[name] = ComponentToRegister;
  4035. return ComponentToRegister;
  4036. }
  4037. /**
  4038. * Get a `Component` based on the name it was registered with.
  4039. *
  4040. * @param {string} name
  4041. * The Name of the component to get.
  4042. *
  4043. * @return {Component}
  4044. * The `Component` that got registered under the given name.
  4045. *
  4046. * @deprecated In `videojs` 6 this will not return `Component`s that were not
  4047. * registered using {@link Component.registerComponent}. Currently we
  4048. * check the global `videojs` object for a `Component` name and
  4049. * return that if it exists.
  4050. */
  4051. ;
  4052. Component.getComponent = function getComponent(name) {
  4053. if (!name) {
  4054. return;
  4055. }
  4056. name = toTitleCase(name);
  4057. if (Component.components_ && Component.components_[name]) {
  4058. return Component.components_[name];
  4059. }
  4060. };
  4061. return Component;
  4062. }();
  4063. /**
  4064. * Whether or not this component supports `requestAnimationFrame`.
  4065. *
  4066. * This is exposed primarily for testing purposes.
  4067. *
  4068. * @private
  4069. * @type {Boolean}
  4070. */
  4071. Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
  4072. Component.registerComponent('Component', Component);
  4073. /**
  4074. * @file browser.js
  4075. * @module browser
  4076. */
  4077. var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
  4078. var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
  4079. var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
  4080. /**
  4081. * Whether or not this device is an iPad.
  4082. *
  4083. * @static
  4084. * @const
  4085. * @type {Boolean}
  4086. */
  4087. var IS_IPAD = /iPad/i.test(USER_AGENT);
  4088. /**
  4089. * Whether or not this device is an iPhone.
  4090. *
  4091. * @static
  4092. * @const
  4093. * @type {Boolean}
  4094. */
  4095. // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
  4096. // to identify iPhones, we need to exclude iPads.
  4097. // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
  4098. var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
  4099. /**
  4100. * Whether or not this device is an iPod.
  4101. *
  4102. * @static
  4103. * @const
  4104. * @type {Boolean}
  4105. */
  4106. var IS_IPOD = /iPod/i.test(USER_AGENT);
  4107. /**
  4108. * Whether or not this is an iOS device.
  4109. *
  4110. * @static
  4111. * @const
  4112. * @type {Boolean}
  4113. */
  4114. var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
  4115. /**
  4116. * The detected iOS version - or `null`.
  4117. *
  4118. * @static
  4119. * @const
  4120. * @type {string|null}
  4121. */
  4122. var IOS_VERSION = function () {
  4123. var match = USER_AGENT.match(/OS (\d+)_/i);
  4124. if (match && match[1]) {
  4125. return match[1];
  4126. }
  4127. return null;
  4128. }();
  4129. /**
  4130. * Whether or not this is an Android device.
  4131. *
  4132. * @static
  4133. * @const
  4134. * @type {Boolean}
  4135. */
  4136. var IS_ANDROID = /Android/i.test(USER_AGENT);
  4137. /**
  4138. * The detected Android version - or `null`.
  4139. *
  4140. * @static
  4141. * @const
  4142. * @type {number|string|null}
  4143. */
  4144. var ANDROID_VERSION = function () {
  4145. // This matches Android Major.Minor.Patch versions
  4146. // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  4147. var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
  4148. if (!match) {
  4149. return null;
  4150. }
  4151. var major = match[1] && parseFloat(match[1]);
  4152. var minor = match[2] && parseFloat(match[2]);
  4153. if (major && minor) {
  4154. return parseFloat(match[1] + '.' + match[2]);
  4155. } else if (major) {
  4156. return major;
  4157. }
  4158. return null;
  4159. }();
  4160. /**
  4161. * Whether or not this is a native Android browser.
  4162. *
  4163. * @static
  4164. * @const
  4165. * @type {Boolean}
  4166. */
  4167. var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
  4168. /**
  4169. * Whether or not this is Mozilla Firefox.
  4170. *
  4171. * @static
  4172. * @const
  4173. * @type {Boolean}
  4174. */
  4175. var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
  4176. /**
  4177. * Whether or not this is Microsoft Edge.
  4178. *
  4179. * @static
  4180. * @const
  4181. * @type {Boolean}
  4182. */
  4183. var IS_EDGE = /Edge/i.test(USER_AGENT);
  4184. /**
  4185. * Whether or not this is Google Chrome.
  4186. *
  4187. * This will also be `true` for Chrome on iOS, which will have different support
  4188. * as it is actually Safari under the hood.
  4189. *
  4190. * @static
  4191. * @const
  4192. * @type {Boolean}
  4193. */
  4194. var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
  4195. /**
  4196. * The detected Google Chrome version - or `null`.
  4197. *
  4198. * @static
  4199. * @const
  4200. * @type {number|null}
  4201. */
  4202. var CHROME_VERSION = function () {
  4203. var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
  4204. if (match && match[2]) {
  4205. return parseFloat(match[2]);
  4206. }
  4207. return null;
  4208. }();
  4209. /**
  4210. * The detected Internet Explorer version - or `null`.
  4211. *
  4212. * @static
  4213. * @const
  4214. * @type {number|null}
  4215. */
  4216. var IE_VERSION = function () {
  4217. var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
  4218. var version = result && parseFloat(result[1]);
  4219. if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
  4220. // IE 11 has a different user agent string than other IE versions
  4221. version = 11.0;
  4222. }
  4223. return version;
  4224. }();
  4225. /**
  4226. * Whether or not this is desktop Safari.
  4227. *
  4228. * @static
  4229. * @const
  4230. * @type {Boolean}
  4231. */
  4232. var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
  4233. /**
  4234. * Whether or not this is any flavor of Safari - including iOS.
  4235. *
  4236. * @static
  4237. * @const
  4238. * @type {Boolean}
  4239. */
  4240. var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
  4241. /**
  4242. * Whether or not this is a Windows machine.
  4243. *
  4244. * @static
  4245. * @const
  4246. * @type {Boolean}
  4247. */
  4248. var IS_WINDOWS = /Windows/i.test(USER_AGENT);
  4249. /**
  4250. * Whether or not this device is touch-enabled.
  4251. *
  4252. * @static
  4253. * @const
  4254. * @type {Boolean}
  4255. */
  4256. var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
  4257. var browser = /*#__PURE__*/Object.freeze({
  4258. IS_IPAD: IS_IPAD,
  4259. IS_IPHONE: IS_IPHONE,
  4260. IS_IPOD: IS_IPOD,
  4261. IS_IOS: IS_IOS,
  4262. IOS_VERSION: IOS_VERSION,
  4263. IS_ANDROID: IS_ANDROID,
  4264. ANDROID_VERSION: ANDROID_VERSION,
  4265. IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
  4266. IS_FIREFOX: IS_FIREFOX,
  4267. IS_EDGE: IS_EDGE,
  4268. IS_CHROME: IS_CHROME,
  4269. CHROME_VERSION: CHROME_VERSION,
  4270. IE_VERSION: IE_VERSION,
  4271. IS_SAFARI: IS_SAFARI,
  4272. IS_ANY_SAFARI: IS_ANY_SAFARI,
  4273. IS_WINDOWS: IS_WINDOWS,
  4274. TOUCH_ENABLED: TOUCH_ENABLED
  4275. });
  4276. /**
  4277. * @file time-ranges.js
  4278. * @module time-ranges
  4279. */
  4280. /**
  4281. * Returns the time for the specified index at the start or end
  4282. * of a TimeRange object.
  4283. *
  4284. * @typedef {Function} TimeRangeIndex
  4285. *
  4286. * @param {number} [index=0]
  4287. * The range number to return the time for.
  4288. *
  4289. * @return {number}
  4290. * The time offset at the specified index.
  4291. *
  4292. * @deprecated The index argument must be provided.
  4293. * In the future, leaving it out will throw an error.
  4294. */
  4295. /**
  4296. * An object that contains ranges of time.
  4297. *
  4298. * @typedef {Object} TimeRange
  4299. *
  4300. * @property {number} length
  4301. * The number of time ranges represented by this object.
  4302. *
  4303. * @property {module:time-ranges~TimeRangeIndex} start
  4304. * Returns the time offset at which a specified time range begins.
  4305. *
  4306. * @property {module:time-ranges~TimeRangeIndex} end
  4307. * Returns the time offset at which a specified time range ends.
  4308. *
  4309. * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
  4310. */
  4311. /**
  4312. * Check if any of the time ranges are over the maximum index.
  4313. *
  4314. * @private
  4315. * @param {string} fnName
  4316. * The function name to use for logging
  4317. *
  4318. * @param {number} index
  4319. * The index to check
  4320. *
  4321. * @param {number} maxIndex
  4322. * The maximum possible index
  4323. *
  4324. * @throws {Error} if the timeRanges provided are over the maxIndex
  4325. */
  4326. function rangeCheck(fnName, index, maxIndex) {
  4327. if (typeof index !== 'number' || index < 0 || index > maxIndex) {
  4328. throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
  4329. }
  4330. }
  4331. /**
  4332. * Get the time for the specified index at the start or end
  4333. * of a TimeRange object.
  4334. *
  4335. * @private
  4336. * @param {string} fnName
  4337. * The function name to use for logging
  4338. *
  4339. * @param {string} valueIndex
  4340. * The property that should be used to get the time. should be
  4341. * 'start' or 'end'
  4342. *
  4343. * @param {Array} ranges
  4344. * An array of time ranges
  4345. *
  4346. * @param {Array} [rangeIndex=0]
  4347. * The index to start the search at
  4348. *
  4349. * @return {number}
  4350. * The time that offset at the specified index.
  4351. *
  4352. * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
  4353. * @throws {Error} if rangeIndex is more than the length of ranges
  4354. */
  4355. function getRange(fnName, valueIndex, ranges, rangeIndex) {
  4356. rangeCheck(fnName, rangeIndex, ranges.length - 1);
  4357. return ranges[rangeIndex][valueIndex];
  4358. }
  4359. /**
  4360. * Create a time range object given ranges of time.
  4361. *
  4362. * @private
  4363. * @param {Array} [ranges]
  4364. * An array of time ranges.
  4365. */
  4366. function createTimeRangesObj(ranges) {
  4367. if (ranges === undefined || ranges.length === 0) {
  4368. return {
  4369. length: 0,
  4370. start: function start() {
  4371. throw new Error('This TimeRanges object is empty');
  4372. },
  4373. end: function end() {
  4374. throw new Error('This TimeRanges object is empty');
  4375. }
  4376. };
  4377. }
  4378. return {
  4379. length: ranges.length,
  4380. start: getRange.bind(null, 'start', 0, ranges),
  4381. end: getRange.bind(null, 'end', 1, ranges)
  4382. };
  4383. }
  4384. /**
  4385. * Create a `TimeRange` object which mimics an
  4386. * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
  4387. *
  4388. * @param {number|Array[]} start
  4389. * The start of a single range (a number) or an array of ranges (an
  4390. * array of arrays of two numbers each).
  4391. *
  4392. * @param {number} end
  4393. * The end of a single range. Cannot be used with the array form of
  4394. * the `start` argument.
  4395. */
  4396. function createTimeRanges(start, end) {
  4397. if (Array.isArray(start)) {
  4398. return createTimeRangesObj(start);
  4399. } else if (start === undefined || end === undefined) {
  4400. return createTimeRangesObj();
  4401. }
  4402. return createTimeRangesObj([[start, end]]);
  4403. }
  4404. /**
  4405. * @file buffer.js
  4406. * @module buffer
  4407. */
  4408. /**
  4409. * Compute the percentage of the media that has been buffered.
  4410. *
  4411. * @param {TimeRange} buffered
  4412. * The current `TimeRange` object representing buffered time ranges
  4413. *
  4414. * @param {number} duration
  4415. * Total duration of the media
  4416. *
  4417. * @return {number}
  4418. * Percent buffered of the total duration in decimal form.
  4419. */
  4420. function bufferedPercent(buffered, duration) {
  4421. var bufferedDuration = 0;
  4422. var start;
  4423. var end;
  4424. if (!duration) {
  4425. return 0;
  4426. }
  4427. if (!buffered || !buffered.length) {
  4428. buffered = createTimeRanges(0, 0);
  4429. }
  4430. for (var i = 0; i < buffered.length; i++) {
  4431. start = buffered.start(i);
  4432. end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
  4433. if (end > duration) {
  4434. end = duration;
  4435. }
  4436. bufferedDuration += end - start;
  4437. }
  4438. return bufferedDuration / duration;
  4439. }
  4440. /**
  4441. * @file fullscreen-api.js
  4442. * @module fullscreen-api
  4443. * @private
  4444. */
  4445. /**
  4446. * Store the browser-specific methods for the fullscreen API.
  4447. *
  4448. * @type {Object}
  4449. * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
  4450. * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
  4451. */
  4452. var FullscreenApi = {}; // browser API methods
  4453. var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
  4454. ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
  4455. ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
  4456. ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
  4457. var specApi = apiMap[0];
  4458. var browserApi;
  4459. var prefixedAPI = false; // determine the supported set of functions
  4460. for (var i = 0; i < apiMap.length; i++) {
  4461. // check for exitFullscreen function
  4462. if (apiMap[i][1] in document) {
  4463. browserApi = apiMap[i];
  4464. break;
  4465. }
  4466. } // map the browser API names to the spec API names
  4467. if (browserApi) {
  4468. for (var _i = 0; _i < browserApi.length; _i++) {
  4469. FullscreenApi[specApi[_i]] = browserApi[_i];
  4470. }
  4471. prefixedAPI = browserApi[0] === specApi[0];
  4472. }
  4473. /**
  4474. * @file media-error.js
  4475. */
  4476. /**
  4477. * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
  4478. *
  4479. * @param {number|string|Object|MediaError} value
  4480. * This can be of multiple types:
  4481. * - number: should be a standard error code
  4482. * - string: an error message (the code will be 0)
  4483. * - Object: arbitrary properties
  4484. * - `MediaError` (native): used to populate a video.js `MediaError` object
  4485. * - `MediaError` (video.js): will return itself if it's already a
  4486. * video.js `MediaError` object.
  4487. *
  4488. * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
  4489. * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
  4490. *
  4491. * @class MediaError
  4492. */
  4493. function MediaError(value) {
  4494. // Allow redundant calls to this constructor to avoid having `instanceof`
  4495. // checks peppered around the code.
  4496. if (value instanceof MediaError) {
  4497. return value;
  4498. }
  4499. if (typeof value === 'number') {
  4500. this.code = value;
  4501. } else if (typeof value === 'string') {
  4502. // default code is zero, so this is a custom error
  4503. this.message = value;
  4504. } else if (isObject(value)) {
  4505. // We assign the `code` property manually because native `MediaError` objects
  4506. // do not expose it as an own/enumerable property of the object.
  4507. if (typeof value.code === 'number') {
  4508. this.code = value.code;
  4509. }
  4510. assign(this, value);
  4511. }
  4512. if (!this.message) {
  4513. this.message = MediaError.defaultMessages[this.code] || '';
  4514. }
  4515. }
  4516. /**
  4517. * The error code that refers two one of the defined `MediaError` types
  4518. *
  4519. * @type {Number}
  4520. */
  4521. MediaError.prototype.code = 0;
  4522. /**
  4523. * An optional message that to show with the error. Message is not part of the HTML5
  4524. * video spec but allows for more informative custom errors.
  4525. *
  4526. * @type {String}
  4527. */
  4528. MediaError.prototype.message = '';
  4529. /**
  4530. * An optional status code that can be set by plugins to allow even more detail about
  4531. * the error. For example a plugin might provide a specific HTTP status code and an
  4532. * error message for that code. Then when the plugin gets that error this class will
  4533. * know how to display an error message for it. This allows a custom message to show
  4534. * up on the `Player` error overlay.
  4535. *
  4536. * @type {Array}
  4537. */
  4538. MediaError.prototype.status = null;
  4539. /**
  4540. * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
  4541. * specification listed under {@link MediaError} for more information.
  4542. *
  4543. * @enum {array}
  4544. * @readonly
  4545. * @property {string} 0 - MEDIA_ERR_CUSTOM
  4546. * @property {string} 1 - MEDIA_ERR_ABORTED
  4547. * @property {string} 2 - MEDIA_ERR_NETWORK
  4548. * @property {string} 3 - MEDIA_ERR_DECODE
  4549. * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
  4550. * @property {string} 5 - MEDIA_ERR_ENCRYPTED
  4551. */
  4552. MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
  4553. /**
  4554. * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
  4555. *
  4556. * @type {Array}
  4557. * @constant
  4558. */
  4559. MediaError.defaultMessages = {
  4560. 1: 'You aborted the media playback',
  4561. 2: 'A network error caused the media download to fail part-way.',
  4562. 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
  4563. 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
  4564. 5: 'The media is encrypted and we do not have the keys to decrypt it.'
  4565. }; // Add types as properties on MediaError
  4566. // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
  4567. for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
  4568. MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
  4569. MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
  4570. } // jsdocs for instance/static members added above
  4571. /**
  4572. * Returns whether an object is `Promise`-like (i.e. has a `then` method).
  4573. *
  4574. * @param {Object} value
  4575. * An object that may or may not be `Promise`-like.
  4576. *
  4577. * @return {boolean}
  4578. * Whether or not the object is `Promise`-like.
  4579. */
  4580. function isPromise(value) {
  4581. return value !== undefined && value !== null && typeof value.then === 'function';
  4582. }
  4583. /**
  4584. * Silence a Promise-like object.
  4585. *
  4586. * This is useful for avoiding non-harmful, but potentially confusing "uncaught
  4587. * play promise" rejection error messages.
  4588. *
  4589. * @param {Object} value
  4590. * An object that may or may not be `Promise`-like.
  4591. */
  4592. function silencePromise(value) {
  4593. if (isPromise(value)) {
  4594. value.then(null, function (e) {});
  4595. }
  4596. }
  4597. /**
  4598. * @file text-track-list-converter.js Utilities for capturing text track state and
  4599. * re-creating tracks based on a capture.
  4600. *
  4601. * @module text-track-list-converter
  4602. */
  4603. /**
  4604. * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
  4605. * represents the {@link TextTrack}'s state.
  4606. *
  4607. * @param {TextTrack} track
  4608. * The text track to query.
  4609. *
  4610. * @return {Object}
  4611. * A serializable javascript representation of the TextTrack.
  4612. * @private
  4613. */
  4614. var trackToJson_ = function trackToJson_(track) {
  4615. var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
  4616. if (track[prop]) {
  4617. acc[prop] = track[prop];
  4618. }
  4619. return acc;
  4620. }, {
  4621. cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
  4622. return {
  4623. startTime: cue.startTime,
  4624. endTime: cue.endTime,
  4625. text: cue.text,
  4626. id: cue.id
  4627. };
  4628. })
  4629. });
  4630. return ret;
  4631. };
  4632. /**
  4633. * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
  4634. * state of all {@link TextTrack}s currently configured. The return array is compatible with
  4635. * {@link text-track-list-converter:jsonToTextTracks}.
  4636. *
  4637. * @param {Tech} tech
  4638. * The tech object to query
  4639. *
  4640. * @return {Array}
  4641. * A serializable javascript representation of the {@link Tech}s
  4642. * {@link TextTrackList}.
  4643. */
  4644. var textTracksToJson = function textTracksToJson(tech) {
  4645. var trackEls = tech.$$('track');
  4646. var trackObjs = Array.prototype.map.call(trackEls, function (t) {
  4647. return t.track;
  4648. });
  4649. var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
  4650. var json = trackToJson_(trackEl.track);
  4651. if (trackEl.src) {
  4652. json.src = trackEl.src;
  4653. }
  4654. return json;
  4655. });
  4656. return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
  4657. return trackObjs.indexOf(track) === -1;
  4658. }).map(trackToJson_));
  4659. };
  4660. /**
  4661. * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
  4662. * object {@link TextTrack} representations.
  4663. *
  4664. * @param {Array} json
  4665. * An array of `TextTrack` representation objects, like those that would be
  4666. * produced by `textTracksToJson`.
  4667. *
  4668. * @param {Tech} tech
  4669. * The `Tech` to create the `TextTrack`s on.
  4670. */
  4671. var jsonToTextTracks = function jsonToTextTracks(json, tech) {
  4672. json.forEach(function (track) {
  4673. var addedTrack = tech.addRemoteTextTrack(track).track;
  4674. if (!track.src && track.cues) {
  4675. track.cues.forEach(function (cue) {
  4676. return addedTrack.addCue(cue);
  4677. });
  4678. }
  4679. });
  4680. return tech.textTracks();
  4681. };
  4682. var textTrackConverter = {
  4683. textTracksToJson: textTracksToJson,
  4684. jsonToTextTracks: jsonToTextTracks,
  4685. trackToJson_: trackToJson_
  4686. };
  4687. var MODAL_CLASS_NAME = 'vjs-modal-dialog';
  4688. /**
  4689. * The `ModalDialog` displays over the video and its controls, which blocks
  4690. * interaction with the player until it is closed.
  4691. *
  4692. * Modal dialogs include a "Close" button and will close when that button
  4693. * is activated - or when ESC is pressed anywhere.
  4694. *
  4695. * @extends Component
  4696. */
  4697. var ModalDialog =
  4698. /*#__PURE__*/
  4699. function (_Component) {
  4700. _inheritsLoose(ModalDialog, _Component);
  4701. /**
  4702. * Create an instance of this class.
  4703. *
  4704. * @param {Player} player
  4705. * The `Player` that this class should be attached to.
  4706. *
  4707. * @param {Object} [options]
  4708. * The key/value store of player options.
  4709. *
  4710. * @param {Mixed} [options.content=undefined]
  4711. * Provide customized content for this modal.
  4712. *
  4713. * @param {string} [options.description]
  4714. * A text description for the modal, primarily for accessibility.
  4715. *
  4716. * @param {boolean} [options.fillAlways=false]
  4717. * Normally, modals are automatically filled only the first time
  4718. * they open. This tells the modal to refresh its content
  4719. * every time it opens.
  4720. *
  4721. * @param {string} [options.label]
  4722. * A text label for the modal, primarily for accessibility.
  4723. *
  4724. * @param {boolean} [options.pauseOnOpen=true]
  4725. * If `true`, playback will will be paused if playing when
  4726. * the modal opens, and resumed when it closes.
  4727. *
  4728. * @param {boolean} [options.temporary=true]
  4729. * If `true`, the modal can only be opened once; it will be
  4730. * disposed as soon as it's closed.
  4731. *
  4732. * @param {boolean} [options.uncloseable=false]
  4733. * If `true`, the user will not be able to close the modal
  4734. * through the UI in the normal ways. Programmatic closing is
  4735. * still possible.
  4736. */
  4737. function ModalDialog(player, options) {
  4738. var _this;
  4739. _this = _Component.call(this, player, options) || this;
  4740. _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
  4741. _this.closeable(!_this.options_.uncloseable);
  4742. _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
  4743. // because we only want the contents of the modal in the contentEl
  4744. // (not the UI elements like the close button).
  4745. _this.contentEl_ = createEl('div', {
  4746. className: MODAL_CLASS_NAME + "-content"
  4747. }, {
  4748. role: 'document'
  4749. });
  4750. _this.descEl_ = createEl('p', {
  4751. className: MODAL_CLASS_NAME + "-description vjs-control-text",
  4752. id: _this.el().getAttribute('aria-describedby')
  4753. });
  4754. textContent(_this.descEl_, _this.description());
  4755. _this.el_.appendChild(_this.descEl_);
  4756. _this.el_.appendChild(_this.contentEl_);
  4757. return _this;
  4758. }
  4759. /**
  4760. * Create the `ModalDialog`'s DOM element
  4761. *
  4762. * @return {Element}
  4763. * The DOM element that gets created.
  4764. */
  4765. var _proto = ModalDialog.prototype;
  4766. _proto.createEl = function createEl$$1() {
  4767. return _Component.prototype.createEl.call(this, 'div', {
  4768. className: this.buildCSSClass(),
  4769. tabIndex: -1
  4770. }, {
  4771. 'aria-describedby': this.id() + "_description",
  4772. 'aria-hidden': 'true',
  4773. 'aria-label': this.label(),
  4774. 'role': 'dialog'
  4775. });
  4776. };
  4777. _proto.dispose = function dispose() {
  4778. this.contentEl_ = null;
  4779. this.descEl_ = null;
  4780. this.previouslyActiveEl_ = null;
  4781. _Component.prototype.dispose.call(this);
  4782. }
  4783. /**
  4784. * Builds the default DOM `className`.
  4785. *
  4786. * @return {string}
  4787. * The DOM `className` for this object.
  4788. */
  4789. ;
  4790. _proto.buildCSSClass = function buildCSSClass() {
  4791. return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
  4792. }
  4793. /**
  4794. * Handles `keydown` events on the document, looking for ESC, which closes
  4795. * the modal.
  4796. *
  4797. * @param {EventTarget~Event} event
  4798. * The keypress that triggered this event.
  4799. *
  4800. * @listens keydown
  4801. */
  4802. ;
  4803. _proto.handleKeyPress = function handleKeyPress(event) {
  4804. if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
  4805. this.close();
  4806. }
  4807. }
  4808. /**
  4809. * Returns the label string for this modal. Primarily used for accessibility.
  4810. *
  4811. * @return {string}
  4812. * the localized or raw label of this modal.
  4813. */
  4814. ;
  4815. _proto.label = function label() {
  4816. return this.localize(this.options_.label || 'Modal Window');
  4817. }
  4818. /**
  4819. * Returns the description string for this modal. Primarily used for
  4820. * accessibility.
  4821. *
  4822. * @return {string}
  4823. * The localized or raw description of this modal.
  4824. */
  4825. ;
  4826. _proto.description = function description() {
  4827. var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
  4828. if (this.closeable()) {
  4829. desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
  4830. }
  4831. return desc;
  4832. }
  4833. /**
  4834. * Opens the modal.
  4835. *
  4836. * @fires ModalDialog#beforemodalopen
  4837. * @fires ModalDialog#modalopen
  4838. */
  4839. ;
  4840. _proto.open = function open() {
  4841. if (!this.opened_) {
  4842. var player = this.player();
  4843. /**
  4844. * Fired just before a `ModalDialog` is opened.
  4845. *
  4846. * @event ModalDialog#beforemodalopen
  4847. * @type {EventTarget~Event}
  4848. */
  4849. this.trigger('beforemodalopen');
  4850. this.opened_ = true; // Fill content if the modal has never opened before and
  4851. // never been filled.
  4852. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
  4853. this.fill();
  4854. } // If the player was playing, pause it and take note of its previously
  4855. // playing state.
  4856. this.wasPlaying_ = !player.paused();
  4857. if (this.options_.pauseOnOpen && this.wasPlaying_) {
  4858. player.pause();
  4859. }
  4860. if (this.closeable()) {
  4861. this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  4862. } // Hide controls and note if they were enabled.
  4863. this.hadControls_ = player.controls();
  4864. player.controls(false);
  4865. this.show();
  4866. this.conditionalFocus_();
  4867. this.el().setAttribute('aria-hidden', 'false');
  4868. /**
  4869. * Fired just after a `ModalDialog` is opened.
  4870. *
  4871. * @event ModalDialog#modalopen
  4872. * @type {EventTarget~Event}
  4873. */
  4874. this.trigger('modalopen');
  4875. this.hasBeenOpened_ = true;
  4876. }
  4877. }
  4878. /**
  4879. * If the `ModalDialog` is currently open or closed.
  4880. *
  4881. * @param {boolean} [value]
  4882. * If given, it will open (`true`) or close (`false`) the modal.
  4883. *
  4884. * @return {boolean}
  4885. * the current open state of the modaldialog
  4886. */
  4887. ;
  4888. _proto.opened = function opened(value) {
  4889. if (typeof value === 'boolean') {
  4890. this[value ? 'open' : 'close']();
  4891. }
  4892. return this.opened_;
  4893. }
  4894. /**
  4895. * Closes the modal, does nothing if the `ModalDialog` is
  4896. * not open.
  4897. *
  4898. * @fires ModalDialog#beforemodalclose
  4899. * @fires ModalDialog#modalclose
  4900. */
  4901. ;
  4902. _proto.close = function close() {
  4903. if (!this.opened_) {
  4904. return;
  4905. }
  4906. var player = this.player();
  4907. /**
  4908. * Fired just before a `ModalDialog` is closed.
  4909. *
  4910. * @event ModalDialog#beforemodalclose
  4911. * @type {EventTarget~Event}
  4912. */
  4913. this.trigger('beforemodalclose');
  4914. this.opened_ = false;
  4915. if (this.wasPlaying_ && this.options_.pauseOnOpen) {
  4916. player.play();
  4917. }
  4918. if (this.closeable()) {
  4919. this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  4920. }
  4921. if (this.hadControls_) {
  4922. player.controls(true);
  4923. }
  4924. this.hide();
  4925. this.el().setAttribute('aria-hidden', 'true');
  4926. /**
  4927. * Fired just after a `ModalDialog` is closed.
  4928. *
  4929. * @event ModalDialog#modalclose
  4930. * @type {EventTarget~Event}
  4931. */
  4932. this.trigger('modalclose');
  4933. this.conditionalBlur_();
  4934. if (this.options_.temporary) {
  4935. this.dispose();
  4936. }
  4937. }
  4938. /**
  4939. * Check to see if the `ModalDialog` is closeable via the UI.
  4940. *
  4941. * @param {boolean} [value]
  4942. * If given as a boolean, it will set the `closeable` option.
  4943. *
  4944. * @return {boolean}
  4945. * Returns the final value of the closable option.
  4946. */
  4947. ;
  4948. _proto.closeable = function closeable(value) {
  4949. if (typeof value === 'boolean') {
  4950. var closeable = this.closeable_ = !!value;
  4951. var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
  4952. if (closeable && !close) {
  4953. // The close button should be a child of the modal - not its
  4954. // content element, so temporarily change the content element.
  4955. var temp = this.contentEl_;
  4956. this.contentEl_ = this.el_;
  4957. close = this.addChild('closeButton', {
  4958. controlText: 'Close Modal Dialog'
  4959. });
  4960. this.contentEl_ = temp;
  4961. this.on(close, 'close', this.close);
  4962. } // If this is being made uncloseable and has a close button, remove it.
  4963. if (!closeable && close) {
  4964. this.off(close, 'close', this.close);
  4965. this.removeChild(close);
  4966. close.dispose();
  4967. }
  4968. }
  4969. return this.closeable_;
  4970. }
  4971. /**
  4972. * Fill the modal's content element with the modal's "content" option.
  4973. * The content element will be emptied before this change takes place.
  4974. */
  4975. ;
  4976. _proto.fill = function fill() {
  4977. this.fillWith(this.content());
  4978. }
  4979. /**
  4980. * Fill the modal's content element with arbitrary content.
  4981. * The content element will be emptied before this change takes place.
  4982. *
  4983. * @fires ModalDialog#beforemodalfill
  4984. * @fires ModalDialog#modalfill
  4985. *
  4986. * @param {Mixed} [content]
  4987. * The same rules apply to this as apply to the `content` option.
  4988. */
  4989. ;
  4990. _proto.fillWith = function fillWith(content) {
  4991. var contentEl = this.contentEl();
  4992. var parentEl = contentEl.parentNode;
  4993. var nextSiblingEl = contentEl.nextSibling;
  4994. /**
  4995. * Fired just before a `ModalDialog` is filled with content.
  4996. *
  4997. * @event ModalDialog#beforemodalfill
  4998. * @type {EventTarget~Event}
  4999. */
  5000. this.trigger('beforemodalfill');
  5001. this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
  5002. // manipulation to avoid modifying the live DOM multiple times.
  5003. parentEl.removeChild(contentEl);
  5004. this.empty();
  5005. insertContent(contentEl, content);
  5006. /**
  5007. * Fired just after a `ModalDialog` is filled with content.
  5008. *
  5009. * @event ModalDialog#modalfill
  5010. * @type {EventTarget~Event}
  5011. */
  5012. this.trigger('modalfill'); // Re-inject the re-filled content element.
  5013. if (nextSiblingEl) {
  5014. parentEl.insertBefore(contentEl, nextSiblingEl);
  5015. } else {
  5016. parentEl.appendChild(contentEl);
  5017. } // make sure that the close button is last in the dialog DOM
  5018. var closeButton = this.getChild('closeButton');
  5019. if (closeButton) {
  5020. parentEl.appendChild(closeButton.el_);
  5021. }
  5022. }
  5023. /**
  5024. * Empties the content element. This happens anytime the modal is filled.
  5025. *
  5026. * @fires ModalDialog#beforemodalempty
  5027. * @fires ModalDialog#modalempty
  5028. */
  5029. ;
  5030. _proto.empty = function empty() {
  5031. /**
  5032. * Fired just before a `ModalDialog` is emptied.
  5033. *
  5034. * @event ModalDialog#beforemodalempty
  5035. * @type {EventTarget~Event}
  5036. */
  5037. this.trigger('beforemodalempty');
  5038. emptyEl(this.contentEl());
  5039. /**
  5040. * Fired just after a `ModalDialog` is emptied.
  5041. *
  5042. * @event ModalDialog#modalempty
  5043. * @type {EventTarget~Event}
  5044. */
  5045. this.trigger('modalempty');
  5046. }
  5047. /**
  5048. * Gets or sets the modal content, which gets normalized before being
  5049. * rendered into the DOM.
  5050. *
  5051. * This does not update the DOM or fill the modal, but it is called during
  5052. * that process.
  5053. *
  5054. * @param {Mixed} [value]
  5055. * If defined, sets the internal content value to be used on the
  5056. * next call(s) to `fill`. This value is normalized before being
  5057. * inserted. To "clear" the internal content value, pass `null`.
  5058. *
  5059. * @return {Mixed}
  5060. * The current content of the modal dialog
  5061. */
  5062. ;
  5063. _proto.content = function content(value) {
  5064. if (typeof value !== 'undefined') {
  5065. this.content_ = value;
  5066. }
  5067. return this.content_;
  5068. }
  5069. /**
  5070. * conditionally focus the modal dialog if focus was previously on the player.
  5071. *
  5072. * @private
  5073. */
  5074. ;
  5075. _proto.conditionalFocus_ = function conditionalFocus_() {
  5076. var activeEl = document.activeElement;
  5077. var playerEl = this.player_.el_;
  5078. this.previouslyActiveEl_ = null;
  5079. if (playerEl.contains(activeEl) || playerEl === activeEl) {
  5080. this.previouslyActiveEl_ = activeEl;
  5081. this.focus();
  5082. this.on(document, 'keydown', this.handleKeyDown);
  5083. }
  5084. }
  5085. /**
  5086. * conditionally blur the element and refocus the last focused element
  5087. *
  5088. * @private
  5089. */
  5090. ;
  5091. _proto.conditionalBlur_ = function conditionalBlur_() {
  5092. if (this.previouslyActiveEl_) {
  5093. this.previouslyActiveEl_.focus();
  5094. this.previouslyActiveEl_ = null;
  5095. }
  5096. this.off(document, 'keydown', this.handleKeyDown);
  5097. }
  5098. /**
  5099. * Keydown handler. Attached when modal is focused.
  5100. *
  5101. * @listens keydown
  5102. */
  5103. ;
  5104. _proto.handleKeyDown = function handleKeyDown(event) {
  5105. // exit early if it isn't a tab key
  5106. if (!keycode.isEventKey(event, 'Tab')) {
  5107. return;
  5108. }
  5109. var focusableEls = this.focusableEls_();
  5110. var activeEl = this.el_.querySelector(':focus');
  5111. var focusIndex;
  5112. for (var i = 0; i < focusableEls.length; i++) {
  5113. if (activeEl === focusableEls[i]) {
  5114. focusIndex = i;
  5115. break;
  5116. }
  5117. }
  5118. if (document.activeElement === this.el_) {
  5119. focusIndex = 0;
  5120. }
  5121. if (event.shiftKey && focusIndex === 0) {
  5122. focusableEls[focusableEls.length - 1].focus();
  5123. event.preventDefault();
  5124. } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
  5125. focusableEls[0].focus();
  5126. event.preventDefault();
  5127. }
  5128. }
  5129. /**
  5130. * get all focusable elements
  5131. *
  5132. * @private
  5133. */
  5134. ;
  5135. _proto.focusableEls_ = function focusableEls_() {
  5136. var allChildren = this.el_.querySelectorAll('*');
  5137. return Array.prototype.filter.call(allChildren, function (child) {
  5138. 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');
  5139. });
  5140. };
  5141. return ModalDialog;
  5142. }(Component);
  5143. /**
  5144. * Default options for `ModalDialog` default options.
  5145. *
  5146. * @type {Object}
  5147. * @private
  5148. */
  5149. ModalDialog.prototype.options_ = {
  5150. pauseOnOpen: true,
  5151. temporary: true
  5152. };
  5153. Component.registerComponent('ModalDialog', ModalDialog);
  5154. /**
  5155. * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
  5156. * {@link VideoTrackList}
  5157. *
  5158. * @extends EventTarget
  5159. */
  5160. var TrackList =
  5161. /*#__PURE__*/
  5162. function (_EventTarget) {
  5163. _inheritsLoose(TrackList, _EventTarget);
  5164. /**
  5165. * Create an instance of this class
  5166. *
  5167. * @param {Track[]} tracks
  5168. * A list of tracks to initialize the list with.
  5169. *
  5170. * @abstract
  5171. */
  5172. function TrackList(tracks) {
  5173. var _this;
  5174. if (tracks === void 0) {
  5175. tracks = [];
  5176. }
  5177. _this = _EventTarget.call(this) || this;
  5178. _this.tracks_ = [];
  5179. /**
  5180. * @memberof TrackList
  5181. * @member {number} length
  5182. * The current number of `Track`s in the this Trackist.
  5183. * @instance
  5184. */
  5185. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'length', {
  5186. get: function get() {
  5187. return this.tracks_.length;
  5188. }
  5189. });
  5190. for (var i = 0; i < tracks.length; i++) {
  5191. _this.addTrack(tracks[i]);
  5192. }
  5193. return _this;
  5194. }
  5195. /**
  5196. * Add a {@link Track} to the `TrackList`
  5197. *
  5198. * @param {Track} track
  5199. * The audio, video, or text track to add to the list.
  5200. *
  5201. * @fires TrackList#addtrack
  5202. */
  5203. var _proto = TrackList.prototype;
  5204. _proto.addTrack = function addTrack(track) {
  5205. var index = this.tracks_.length;
  5206. if (!('' + index in this)) {
  5207. Object.defineProperty(this, index, {
  5208. get: function get() {
  5209. return this.tracks_[index];
  5210. }
  5211. });
  5212. } // Do not add duplicate tracks
  5213. if (this.tracks_.indexOf(track) === -1) {
  5214. this.tracks_.push(track);
  5215. /**
  5216. * Triggered when a track is added to a track list.
  5217. *
  5218. * @event TrackList#addtrack
  5219. * @type {EventTarget~Event}
  5220. * @property {Track} track
  5221. * A reference to track that was added.
  5222. */
  5223. this.trigger({
  5224. track: track,
  5225. type: 'addtrack',
  5226. target: this
  5227. });
  5228. }
  5229. }
  5230. /**
  5231. * Remove a {@link Track} from the `TrackList`
  5232. *
  5233. * @param {Track} rtrack
  5234. * The audio, video, or text track to remove from the list.
  5235. *
  5236. * @fires TrackList#removetrack
  5237. */
  5238. ;
  5239. _proto.removeTrack = function removeTrack(rtrack) {
  5240. var track;
  5241. for (var i = 0, l = this.length; i < l; i++) {
  5242. if (this[i] === rtrack) {
  5243. track = this[i];
  5244. if (track.off) {
  5245. track.off();
  5246. }
  5247. this.tracks_.splice(i, 1);
  5248. break;
  5249. }
  5250. }
  5251. if (!track) {
  5252. return;
  5253. }
  5254. /**
  5255. * Triggered when a track is removed from track list.
  5256. *
  5257. * @event TrackList#removetrack
  5258. * @type {EventTarget~Event}
  5259. * @property {Track} track
  5260. * A reference to track that was removed.
  5261. */
  5262. this.trigger({
  5263. track: track,
  5264. type: 'removetrack',
  5265. target: this
  5266. });
  5267. }
  5268. /**
  5269. * Get a Track from the TrackList by a tracks id
  5270. *
  5271. * @param {string} id - the id of the track to get
  5272. * @method getTrackById
  5273. * @return {Track}
  5274. * @private
  5275. */
  5276. ;
  5277. _proto.getTrackById = function getTrackById(id) {
  5278. var result = null;
  5279. for (var i = 0, l = this.length; i < l; i++) {
  5280. var track = this[i];
  5281. if (track.id === id) {
  5282. result = track;
  5283. break;
  5284. }
  5285. }
  5286. return result;
  5287. };
  5288. return TrackList;
  5289. }(EventTarget);
  5290. /**
  5291. * Triggered when a different track is selected/enabled.
  5292. *
  5293. * @event TrackList#change
  5294. * @type {EventTarget~Event}
  5295. */
  5296. /**
  5297. * Events that can be called with on + eventName. See {@link EventHandler}.
  5298. *
  5299. * @property {Object} TrackList#allowedEvents_
  5300. * @private
  5301. */
  5302. TrackList.prototype.allowedEvents_ = {
  5303. change: 'change',
  5304. addtrack: 'addtrack',
  5305. removetrack: 'removetrack'
  5306. }; // emulate attribute EventHandler support to allow for feature detection
  5307. for (var event in TrackList.prototype.allowedEvents_) {
  5308. TrackList.prototype['on' + event] = null;
  5309. }
  5310. /**
  5311. * Anywhere we call this function we diverge from the spec
  5312. * as we only support one enabled audiotrack at a time
  5313. *
  5314. * @param {AudioTrackList} list
  5315. * list to work on
  5316. *
  5317. * @param {AudioTrack} track
  5318. * The track to skip
  5319. *
  5320. * @private
  5321. */
  5322. var disableOthers = function disableOthers(list, track) {
  5323. for (var i = 0; i < list.length; i++) {
  5324. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5325. continue;
  5326. } // another audio track is enabled, disable it
  5327. list[i].enabled = false;
  5328. }
  5329. };
  5330. /**
  5331. * The current list of {@link AudioTrack} for a media file.
  5332. *
  5333. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
  5334. * @extends TrackList
  5335. */
  5336. var AudioTrackList =
  5337. /*#__PURE__*/
  5338. function (_TrackList) {
  5339. _inheritsLoose(AudioTrackList, _TrackList);
  5340. /**
  5341. * Create an instance of this class.
  5342. *
  5343. * @param {AudioTrack[]} [tracks=[]]
  5344. * A list of `AudioTrack` to instantiate the list with.
  5345. */
  5346. function AudioTrackList(tracks) {
  5347. var _this;
  5348. if (tracks === void 0) {
  5349. tracks = [];
  5350. }
  5351. // make sure only 1 track is enabled
  5352. // sorted from last index to first index
  5353. for (var i = tracks.length - 1; i >= 0; i--) {
  5354. if (tracks[i].enabled) {
  5355. disableOthers(tracks, tracks[i]);
  5356. break;
  5357. }
  5358. }
  5359. _this = _TrackList.call(this, tracks) || this;
  5360. _this.changing_ = false;
  5361. return _this;
  5362. }
  5363. /**
  5364. * Add an {@link AudioTrack} to the `AudioTrackList`.
  5365. *
  5366. * @param {AudioTrack} track
  5367. * The AudioTrack to add to the list
  5368. *
  5369. * @fires TrackList#addtrack
  5370. */
  5371. var _proto = AudioTrackList.prototype;
  5372. _proto.addTrack = function addTrack(track) {
  5373. var _this2 = this;
  5374. if (track.enabled) {
  5375. disableOthers(this, track);
  5376. }
  5377. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5378. if (!track.addEventListener) {
  5379. return;
  5380. }
  5381. track.enabledChange_ = function () {
  5382. // when we are disabling other tracks (since we don't support
  5383. // more than one track at a time) we will set changing_
  5384. // to true so that we don't trigger additional change events
  5385. if (_this2.changing_) {
  5386. return;
  5387. }
  5388. _this2.changing_ = true;
  5389. disableOthers(_this2, track);
  5390. _this2.changing_ = false;
  5391. _this2.trigger('change');
  5392. };
  5393. /**
  5394. * @listens AudioTrack#enabledchange
  5395. * @fires TrackList#change
  5396. */
  5397. track.addEventListener('enabledchange', track.enabledChange_);
  5398. };
  5399. _proto.removeTrack = function removeTrack(rtrack) {
  5400. _TrackList.prototype.removeTrack.call(this, rtrack);
  5401. if (rtrack.removeEventListener && rtrack.enabledChange_) {
  5402. rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
  5403. rtrack.enabledChange_ = null;
  5404. }
  5405. };
  5406. return AudioTrackList;
  5407. }(TrackList);
  5408. /**
  5409. * Un-select all other {@link VideoTrack}s that are selected.
  5410. *
  5411. * @param {VideoTrackList} list
  5412. * list to work on
  5413. *
  5414. * @param {VideoTrack} track
  5415. * The track to skip
  5416. *
  5417. * @private
  5418. */
  5419. var disableOthers$1 = function disableOthers(list, track) {
  5420. for (var i = 0; i < list.length; i++) {
  5421. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5422. continue;
  5423. } // another video track is enabled, disable it
  5424. list[i].selected = false;
  5425. }
  5426. };
  5427. /**
  5428. * The current list of {@link VideoTrack} for a video.
  5429. *
  5430. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
  5431. * @extends TrackList
  5432. */
  5433. var VideoTrackList =
  5434. /*#__PURE__*/
  5435. function (_TrackList) {
  5436. _inheritsLoose(VideoTrackList, _TrackList);
  5437. /**
  5438. * Create an instance of this class.
  5439. *
  5440. * @param {VideoTrack[]} [tracks=[]]
  5441. * A list of `VideoTrack` to instantiate the list with.
  5442. */
  5443. function VideoTrackList(tracks) {
  5444. var _this;
  5445. if (tracks === void 0) {
  5446. tracks = [];
  5447. }
  5448. // make sure only 1 track is enabled
  5449. // sorted from last index to first index
  5450. for (var i = tracks.length - 1; i >= 0; i--) {
  5451. if (tracks[i].selected) {
  5452. disableOthers$1(tracks, tracks[i]);
  5453. break;
  5454. }
  5455. }
  5456. _this = _TrackList.call(this, tracks) || this;
  5457. _this.changing_ = false;
  5458. /**
  5459. * @member {number} VideoTrackList#selectedIndex
  5460. * The current index of the selected {@link VideoTrack`}.
  5461. */
  5462. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selectedIndex', {
  5463. get: function get() {
  5464. for (var _i = 0; _i < this.length; _i++) {
  5465. if (this[_i].selected) {
  5466. return _i;
  5467. }
  5468. }
  5469. return -1;
  5470. },
  5471. set: function set() {}
  5472. });
  5473. return _this;
  5474. }
  5475. /**
  5476. * Add a {@link VideoTrack} to the `VideoTrackList`.
  5477. *
  5478. * @param {VideoTrack} track
  5479. * The VideoTrack to add to the list
  5480. *
  5481. * @fires TrackList#addtrack
  5482. */
  5483. var _proto = VideoTrackList.prototype;
  5484. _proto.addTrack = function addTrack(track) {
  5485. var _this2 = this;
  5486. if (track.selected) {
  5487. disableOthers$1(this, track);
  5488. }
  5489. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5490. if (!track.addEventListener) {
  5491. return;
  5492. }
  5493. track.selectedChange_ = function () {
  5494. if (_this2.changing_) {
  5495. return;
  5496. }
  5497. _this2.changing_ = true;
  5498. disableOthers$1(_this2, track);
  5499. _this2.changing_ = false;
  5500. _this2.trigger('change');
  5501. };
  5502. /**
  5503. * @listens VideoTrack#selectedchange
  5504. * @fires TrackList#change
  5505. */
  5506. track.addEventListener('selectedchange', track.selectedChange_);
  5507. };
  5508. _proto.removeTrack = function removeTrack(rtrack) {
  5509. _TrackList.prototype.removeTrack.call(this, rtrack);
  5510. if (rtrack.removeEventListener && rtrack.selectedChange_) {
  5511. rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
  5512. rtrack.selectedChange_ = null;
  5513. }
  5514. };
  5515. return VideoTrackList;
  5516. }(TrackList);
  5517. /**
  5518. * The current list of {@link TextTrack} for a media file.
  5519. *
  5520. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
  5521. * @extends TrackList
  5522. */
  5523. var TextTrackList =
  5524. /*#__PURE__*/
  5525. function (_TrackList) {
  5526. _inheritsLoose(TextTrackList, _TrackList);
  5527. function TextTrackList() {
  5528. return _TrackList.apply(this, arguments) || this;
  5529. }
  5530. var _proto = TextTrackList.prototype;
  5531. /**
  5532. * Add a {@link TextTrack} to the `TextTrackList`
  5533. *
  5534. * @param {TextTrack} track
  5535. * The text track to add to the list.
  5536. *
  5537. * @fires TrackList#addtrack
  5538. */
  5539. _proto.addTrack = function addTrack(track) {
  5540. var _this = this;
  5541. _TrackList.prototype.addTrack.call(this, track);
  5542. if (!this.queueChange_) {
  5543. this.queueChange_ = function () {
  5544. return _this.queueTrigger('change');
  5545. };
  5546. }
  5547. if (!this.triggerSelectedlanguagechange) {
  5548. this.triggerSelectedlanguagechange_ = function () {
  5549. return _this.trigger('selectedlanguagechange');
  5550. };
  5551. }
  5552. /**
  5553. * @listens TextTrack#modechange
  5554. * @fires TrackList#change
  5555. */
  5556. track.addEventListener('modechange', this.queueChange_);
  5557. var nonLanguageTextTrackKind = ['metadata', 'chapters'];
  5558. if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
  5559. track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
  5560. }
  5561. };
  5562. _proto.removeTrack = function removeTrack(rtrack) {
  5563. _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
  5564. if (rtrack.removeEventListener) {
  5565. if (this.queueChange_) {
  5566. rtrack.removeEventListener('modechange', this.queueChange_);
  5567. }
  5568. if (this.selectedlanguagechange_) {
  5569. rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
  5570. }
  5571. }
  5572. };
  5573. return TextTrackList;
  5574. }(TrackList);
  5575. /**
  5576. * @file html-track-element-list.js
  5577. */
  5578. /**
  5579. * The current list of {@link HtmlTrackElement}s.
  5580. */
  5581. var HtmlTrackElementList =
  5582. /*#__PURE__*/
  5583. function () {
  5584. /**
  5585. * Create an instance of this class.
  5586. *
  5587. * @param {HtmlTrackElement[]} [tracks=[]]
  5588. * A list of `HtmlTrackElement` to instantiate the list with.
  5589. */
  5590. function HtmlTrackElementList(trackElements) {
  5591. if (trackElements === void 0) {
  5592. trackElements = [];
  5593. }
  5594. this.trackElements_ = [];
  5595. /**
  5596. * @memberof HtmlTrackElementList
  5597. * @member {number} length
  5598. * The current number of `Track`s in the this Trackist.
  5599. * @instance
  5600. */
  5601. Object.defineProperty(this, 'length', {
  5602. get: function get() {
  5603. return this.trackElements_.length;
  5604. }
  5605. });
  5606. for (var i = 0, length = trackElements.length; i < length; i++) {
  5607. this.addTrackElement_(trackElements[i]);
  5608. }
  5609. }
  5610. /**
  5611. * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
  5612. *
  5613. * @param {HtmlTrackElement} trackElement
  5614. * The track element to add to the list.
  5615. *
  5616. * @private
  5617. */
  5618. var _proto = HtmlTrackElementList.prototype;
  5619. _proto.addTrackElement_ = function addTrackElement_(trackElement) {
  5620. var index = this.trackElements_.length;
  5621. if (!('' + index in this)) {
  5622. Object.defineProperty(this, index, {
  5623. get: function get() {
  5624. return this.trackElements_[index];
  5625. }
  5626. });
  5627. } // Do not add duplicate elements
  5628. if (this.trackElements_.indexOf(trackElement) === -1) {
  5629. this.trackElements_.push(trackElement);
  5630. }
  5631. }
  5632. /**
  5633. * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
  5634. * {@link TextTrack}.
  5635. *
  5636. * @param {TextTrack} track
  5637. * The track associated with a track element.
  5638. *
  5639. * @return {HtmlTrackElement|undefined}
  5640. * The track element that was found or undefined.
  5641. *
  5642. * @private
  5643. */
  5644. ;
  5645. _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
  5646. var trackElement_;
  5647. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5648. if (track === this.trackElements_[i].track) {
  5649. trackElement_ = this.trackElements_[i];
  5650. break;
  5651. }
  5652. }
  5653. return trackElement_;
  5654. }
  5655. /**
  5656. * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
  5657. *
  5658. * @param {HtmlTrackElement} trackElement
  5659. * The track element to remove from the list.
  5660. *
  5661. * @private
  5662. */
  5663. ;
  5664. _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
  5665. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5666. if (trackElement === this.trackElements_[i]) {
  5667. if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
  5668. this.trackElements_[i].track.off();
  5669. }
  5670. if (typeof this.trackElements_[i].off === 'function') {
  5671. this.trackElements_[i].off();
  5672. }
  5673. this.trackElements_.splice(i, 1);
  5674. break;
  5675. }
  5676. }
  5677. };
  5678. return HtmlTrackElementList;
  5679. }();
  5680. /**
  5681. * @file text-track-cue-list.js
  5682. */
  5683. /**
  5684. * @typedef {Object} TextTrackCueList~TextTrackCue
  5685. *
  5686. * @property {string} id
  5687. * The unique id for this text track cue
  5688. *
  5689. * @property {number} startTime
  5690. * The start time for this text track cue
  5691. *
  5692. * @property {number} endTime
  5693. * The end time for this text track cue
  5694. *
  5695. * @property {boolean} pauseOnExit
  5696. * Pause when the end time is reached if true.
  5697. *
  5698. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
  5699. */
  5700. /**
  5701. * A List of TextTrackCues.
  5702. *
  5703. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
  5704. */
  5705. var TextTrackCueList =
  5706. /*#__PURE__*/
  5707. function () {
  5708. /**
  5709. * Create an instance of this class..
  5710. *
  5711. * @param {Array} cues
  5712. * A list of cues to be initialized with
  5713. */
  5714. function TextTrackCueList(cues) {
  5715. TextTrackCueList.prototype.setCues_.call(this, cues);
  5716. /**
  5717. * @memberof TextTrackCueList
  5718. * @member {number} length
  5719. * The current number of `TextTrackCue`s in the TextTrackCueList.
  5720. * @instance
  5721. */
  5722. Object.defineProperty(this, 'length', {
  5723. get: function get() {
  5724. return this.length_;
  5725. }
  5726. });
  5727. }
  5728. /**
  5729. * A setter for cues in this list. Creates getters
  5730. * an an index for the cues.
  5731. *
  5732. * @param {Array} cues
  5733. * An array of cues to set
  5734. *
  5735. * @private
  5736. */
  5737. var _proto = TextTrackCueList.prototype;
  5738. _proto.setCues_ = function setCues_(cues) {
  5739. var oldLength = this.length || 0;
  5740. var i = 0;
  5741. var l = cues.length;
  5742. this.cues_ = cues;
  5743. this.length_ = cues.length;
  5744. var defineProp = function defineProp(index) {
  5745. if (!('' + index in this)) {
  5746. Object.defineProperty(this, '' + index, {
  5747. get: function get() {
  5748. return this.cues_[index];
  5749. }
  5750. });
  5751. }
  5752. };
  5753. if (oldLength < l) {
  5754. i = oldLength;
  5755. for (; i < l; i++) {
  5756. defineProp.call(this, i);
  5757. }
  5758. }
  5759. }
  5760. /**
  5761. * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
  5762. *
  5763. * @param {string} id
  5764. * The id of the cue that should be searched for.
  5765. *
  5766. * @return {TextTrackCueList~TextTrackCue|null}
  5767. * A single cue or null if none was found.
  5768. */
  5769. ;
  5770. _proto.getCueById = function getCueById(id) {
  5771. var result = null;
  5772. for (var i = 0, l = this.length; i < l; i++) {
  5773. var cue = this[i];
  5774. if (cue.id === id) {
  5775. result = cue;
  5776. break;
  5777. }
  5778. }
  5779. return result;
  5780. };
  5781. return TextTrackCueList;
  5782. }();
  5783. /**
  5784. * @file track-kinds.js
  5785. */
  5786. /**
  5787. * All possible `VideoTrackKind`s
  5788. *
  5789. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
  5790. * @typedef VideoTrack~Kind
  5791. * @enum
  5792. */
  5793. var VideoTrackKind = {
  5794. alternative: 'alternative',
  5795. captions: 'captions',
  5796. main: 'main',
  5797. sign: 'sign',
  5798. subtitles: 'subtitles',
  5799. commentary: 'commentary'
  5800. };
  5801. /**
  5802. * All possible `AudioTrackKind`s
  5803. *
  5804. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
  5805. * @typedef AudioTrack~Kind
  5806. * @enum
  5807. */
  5808. var AudioTrackKind = {
  5809. 'alternative': 'alternative',
  5810. 'descriptions': 'descriptions',
  5811. 'main': 'main',
  5812. 'main-desc': 'main-desc',
  5813. 'translation': 'translation',
  5814. 'commentary': 'commentary'
  5815. };
  5816. /**
  5817. * All possible `TextTrackKind`s
  5818. *
  5819. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
  5820. * @typedef TextTrack~Kind
  5821. * @enum
  5822. */
  5823. var TextTrackKind = {
  5824. subtitles: 'subtitles',
  5825. captions: 'captions',
  5826. descriptions: 'descriptions',
  5827. chapters: 'chapters',
  5828. metadata: 'metadata'
  5829. };
  5830. /**
  5831. * All possible `TextTrackMode`s
  5832. *
  5833. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
  5834. * @typedef TextTrack~Mode
  5835. * @enum
  5836. */
  5837. var TextTrackMode = {
  5838. disabled: 'disabled',
  5839. hidden: 'hidden',
  5840. showing: 'showing'
  5841. };
  5842. /**
  5843. * A Track class that contains all of the common functionality for {@link AudioTrack},
  5844. * {@link VideoTrack}, and {@link TextTrack}.
  5845. *
  5846. * > Note: This class should not be used directly
  5847. *
  5848. * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
  5849. * @extends EventTarget
  5850. * @abstract
  5851. */
  5852. var Track =
  5853. /*#__PURE__*/
  5854. function (_EventTarget) {
  5855. _inheritsLoose(Track, _EventTarget);
  5856. /**
  5857. * Create an instance of this class.
  5858. *
  5859. * @param {Object} [options={}]
  5860. * Object of option names and values
  5861. *
  5862. * @param {string} [options.kind='']
  5863. * A valid kind for the track type you are creating.
  5864. *
  5865. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  5866. * A unique id for this AudioTrack.
  5867. *
  5868. * @param {string} [options.label='']
  5869. * The menu label for this track.
  5870. *
  5871. * @param {string} [options.language='']
  5872. * A valid two character language code.
  5873. *
  5874. * @abstract
  5875. */
  5876. function Track(options) {
  5877. var _this;
  5878. if (options === void 0) {
  5879. options = {};
  5880. }
  5881. _this = _EventTarget.call(this) || this;
  5882. var trackProps = {
  5883. id: options.id || 'vjs_track_' + newGUID(),
  5884. kind: options.kind || '',
  5885. label: options.label || '',
  5886. language: options.language || ''
  5887. };
  5888. /**
  5889. * @memberof Track
  5890. * @member {string} id
  5891. * The id of this track. Cannot be changed after creation.
  5892. * @instance
  5893. *
  5894. * @readonly
  5895. */
  5896. /**
  5897. * @memberof Track
  5898. * @member {string} kind
  5899. * The kind of track that this is. Cannot be changed after creation.
  5900. * @instance
  5901. *
  5902. * @readonly
  5903. */
  5904. /**
  5905. * @memberof Track
  5906. * @member {string} label
  5907. * The label of this track. Cannot be changed after creation.
  5908. * @instance
  5909. *
  5910. * @readonly
  5911. */
  5912. /**
  5913. * @memberof Track
  5914. * @member {string} language
  5915. * The two letter language code for this track. Cannot be changed after
  5916. * creation.
  5917. * @instance
  5918. *
  5919. * @readonly
  5920. */
  5921. var _loop = function _loop(key) {
  5922. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), key, {
  5923. get: function get() {
  5924. return trackProps[key];
  5925. },
  5926. set: function set() {}
  5927. });
  5928. };
  5929. for (var key in trackProps) {
  5930. _loop(key);
  5931. }
  5932. return _this;
  5933. }
  5934. return Track;
  5935. }(EventTarget);
  5936. /**
  5937. * @file url.js
  5938. * @module url
  5939. */
  5940. /**
  5941. * @typedef {Object} url:URLObject
  5942. *
  5943. * @property {string} protocol
  5944. * The protocol of the url that was parsed.
  5945. *
  5946. * @property {string} hostname
  5947. * The hostname of the url that was parsed.
  5948. *
  5949. * @property {string} port
  5950. * The port of the url that was parsed.
  5951. *
  5952. * @property {string} pathname
  5953. * The pathname of the url that was parsed.
  5954. *
  5955. * @property {string} search
  5956. * The search query of the url that was parsed.
  5957. *
  5958. * @property {string} hash
  5959. * The hash of the url that was parsed.
  5960. *
  5961. * @property {string} host
  5962. * The host of the url that was parsed.
  5963. */
  5964. /**
  5965. * Resolve and parse the elements of a URL.
  5966. *
  5967. * @function
  5968. * @param {String} url
  5969. * The url to parse
  5970. *
  5971. * @return {url:URLObject}
  5972. * An object of url details
  5973. */
  5974. var parseUrl = function parseUrl(url) {
  5975. var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
  5976. var a = document.createElement('a');
  5977. a.href = url; // IE8 (and 9?) Fix
  5978. // ie8 doesn't parse the URL correctly until the anchor is actually
  5979. // added to the body, and an innerHTML is needed to trigger the parsing
  5980. var addToBody = a.host === '' && a.protocol !== 'file:';
  5981. var div;
  5982. if (addToBody) {
  5983. div = document.createElement('div');
  5984. div.innerHTML = "<a href=\"" + url + "\"></a>";
  5985. a = div.firstChild; // prevent the div from affecting layout
  5986. div.setAttribute('style', 'display:none; position:absolute;');
  5987. document.body.appendChild(div);
  5988. } // Copy the specific URL properties to a new object
  5989. // This is also needed for IE8 because the anchor loses its
  5990. // properties when it's removed from the dom
  5991. var details = {};
  5992. for (var i = 0; i < props.length; i++) {
  5993. details[props[i]] = a[props[i]];
  5994. } // IE9 adds the port to the host property unlike everyone else. If
  5995. // a port identifier is added for standard ports, strip it.
  5996. if (details.protocol === 'http:') {
  5997. details.host = details.host.replace(/:80$/, '');
  5998. }
  5999. if (details.protocol === 'https:') {
  6000. details.host = details.host.replace(/:443$/, '');
  6001. }
  6002. if (!details.protocol) {
  6003. details.protocol = window$1.location.protocol;
  6004. }
  6005. if (addToBody) {
  6006. document.body.removeChild(div);
  6007. }
  6008. return details;
  6009. };
  6010. /**
  6011. * Get absolute version of relative URL. Used to tell Flash the correct URL.
  6012. *
  6013. * @function
  6014. * @param {string} url
  6015. * URL to make absolute
  6016. *
  6017. * @return {string}
  6018. * Absolute URL
  6019. *
  6020. * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  6021. */
  6022. var getAbsoluteURL = function getAbsoluteURL(url) {
  6023. // Check if absolute URL
  6024. if (!url.match(/^https?:\/\//)) {
  6025. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  6026. var div = document.createElement('div');
  6027. div.innerHTML = "<a href=\"" + url + "\">x</a>";
  6028. url = div.firstChild.href;
  6029. }
  6030. return url;
  6031. };
  6032. /**
  6033. * Returns the extension of the passed file name. It will return an empty string
  6034. * if passed an invalid path.
  6035. *
  6036. * @function
  6037. * @param {string} path
  6038. * The fileName path like '/path/to/file.mp4'
  6039. *
  6040. * @return {string}
  6041. * The extension in lower case or an empty string if no
  6042. * extension could be found.
  6043. */
  6044. var getFileExtension = function getFileExtension(path) {
  6045. if (typeof path === 'string') {
  6046. var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
  6047. var pathParts = splitPathRe.exec(path);
  6048. if (pathParts) {
  6049. return pathParts.pop().toLowerCase();
  6050. }
  6051. }
  6052. return '';
  6053. };
  6054. /**
  6055. * Returns whether the url passed is a cross domain request or not.
  6056. *
  6057. * @function
  6058. * @param {string} url
  6059. * The url to check.
  6060. *
  6061. * @return {boolean}
  6062. * Whether it is a cross domain request or not.
  6063. */
  6064. var isCrossOrigin = function isCrossOrigin(url) {
  6065. var winLoc = window$1.location;
  6066. var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
  6067. var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
  6068. // IE8 doesn't know location.origin, so we won't rely on it here
  6069. var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
  6070. return crossOrigin;
  6071. };
  6072. var Url = /*#__PURE__*/Object.freeze({
  6073. parseUrl: parseUrl,
  6074. getAbsoluteURL: getAbsoluteURL,
  6075. getFileExtension: getFileExtension,
  6076. isCrossOrigin: isCrossOrigin
  6077. });
  6078. /**
  6079. * Takes a webvtt file contents and parses it into cues
  6080. *
  6081. * @param {string} srcContent
  6082. * webVTT file contents
  6083. *
  6084. * @param {TextTrack} track
  6085. * TextTrack to add cues to. Cues come from the srcContent.
  6086. *
  6087. * @private
  6088. */
  6089. var parseCues = function parseCues(srcContent, track) {
  6090. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
  6091. var errors = [];
  6092. parser.oncue = function (cue) {
  6093. track.addCue(cue);
  6094. };
  6095. parser.onparsingerror = function (error) {
  6096. errors.push(error);
  6097. };
  6098. parser.onflush = function () {
  6099. track.trigger({
  6100. type: 'loadeddata',
  6101. target: track
  6102. });
  6103. };
  6104. parser.parse(srcContent);
  6105. if (errors.length > 0) {
  6106. if (window$1.console && window$1.console.groupCollapsed) {
  6107. window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
  6108. }
  6109. errors.forEach(function (error) {
  6110. return log.error(error);
  6111. });
  6112. if (window$1.console && window$1.console.groupEnd) {
  6113. window$1.console.groupEnd();
  6114. }
  6115. }
  6116. parser.flush();
  6117. };
  6118. /**
  6119. * Load a `TextTrack` from a specified url.
  6120. *
  6121. * @param {string} src
  6122. * Url to load track from.
  6123. *
  6124. * @param {TextTrack} track
  6125. * Track to add cues to. Comes from the content at the end of `url`.
  6126. *
  6127. * @private
  6128. */
  6129. var loadTrack = function loadTrack(src, track) {
  6130. var opts = {
  6131. uri: src
  6132. };
  6133. var crossOrigin = isCrossOrigin(src);
  6134. if (crossOrigin) {
  6135. opts.cors = crossOrigin;
  6136. }
  6137. xhr(opts, bind(this, function (err, response, responseBody) {
  6138. if (err) {
  6139. return log.error(err, response);
  6140. }
  6141. track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  6142. // NOTE: this is only used for the alt/video.novtt.js build
  6143. if (typeof window$1.WebVTT !== 'function') {
  6144. if (track.tech_) {
  6145. // to prevent use before define eslint error, we define loadHandler
  6146. // as a let here
  6147. var loadHandler;
  6148. var errorHandler = function errorHandler() {
  6149. log.error("vttjs failed to load, stopping trying to process " + track.src);
  6150. track.tech_.off('vttjsloaded', loadHandler);
  6151. };
  6152. loadHandler = function loadHandler() {
  6153. track.tech_.off('vttjserror', errorHandler);
  6154. return parseCues(responseBody, track);
  6155. };
  6156. track.tech_.one('vttjsloaded', loadHandler);
  6157. track.tech_.one('vttjserror', errorHandler);
  6158. }
  6159. } else {
  6160. parseCues(responseBody, track);
  6161. }
  6162. }));
  6163. };
  6164. /**
  6165. * A representation of a single `TextTrack`.
  6166. *
  6167. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
  6168. * @extends Track
  6169. */
  6170. var TextTrack =
  6171. /*#__PURE__*/
  6172. function (_Track) {
  6173. _inheritsLoose(TextTrack, _Track);
  6174. /**
  6175. * Create an instance of this class.
  6176. *
  6177. * @param {Object} options={}
  6178. * Object of option names and values
  6179. *
  6180. * @param {Tech} options.tech
  6181. * A reference to the tech that owns this TextTrack.
  6182. *
  6183. * @param {TextTrack~Kind} [options.kind='subtitles']
  6184. * A valid text track kind.
  6185. *
  6186. * @param {TextTrack~Mode} [options.mode='disabled']
  6187. * A valid text track mode.
  6188. *
  6189. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6190. * A unique id for this TextTrack.
  6191. *
  6192. * @param {string} [options.label='']
  6193. * The menu label for this track.
  6194. *
  6195. * @param {string} [options.language='']
  6196. * A valid two character language code.
  6197. *
  6198. * @param {string} [options.srclang='']
  6199. * A valid two character language code. An alternative, but deprioritized
  6200. * version of `options.language`
  6201. *
  6202. * @param {string} [options.src]
  6203. * A url to TextTrack cues.
  6204. *
  6205. * @param {boolean} [options.default]
  6206. * If this track should default to on or off.
  6207. */
  6208. function TextTrack(options) {
  6209. var _this;
  6210. if (options === void 0) {
  6211. options = {};
  6212. }
  6213. if (!options.tech) {
  6214. throw new Error('A tech was not provided.');
  6215. }
  6216. var settings = mergeOptions(options, {
  6217. kind: TextTrackKind[options.kind] || 'subtitles',
  6218. language: options.language || options.srclang || ''
  6219. });
  6220. var mode = TextTrackMode[settings.mode] || 'disabled';
  6221. var default_ = settings.default;
  6222. if (settings.kind === 'metadata' || settings.kind === 'chapters') {
  6223. mode = 'hidden';
  6224. }
  6225. _this = _Track.call(this, settings) || this;
  6226. _this.tech_ = settings.tech;
  6227. _this.cues_ = [];
  6228. _this.activeCues_ = [];
  6229. var cues = new TextTrackCueList(_this.cues_);
  6230. var activeCues = new TextTrackCueList(_this.activeCues_);
  6231. var changed = false;
  6232. var timeupdateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
  6233. // Accessing this.activeCues for the side-effects of updating itself
  6234. // due to its nature as a getter function. Do not remove or cues will
  6235. // stop updating!
  6236. // Use the setter to prevent deletion from uglify (pure_getters rule)
  6237. this.activeCues = this.activeCues;
  6238. if (changed) {
  6239. this.trigger('cuechange');
  6240. changed = false;
  6241. }
  6242. });
  6243. if (mode !== 'disabled') {
  6244. _this.tech_.ready(function () {
  6245. _this.tech_.on('timeupdate', timeupdateHandler);
  6246. }, true);
  6247. }
  6248. Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
  6249. /**
  6250. * @memberof TextTrack
  6251. * @member {boolean} default
  6252. * If this track was set to be on or off by default. Cannot be changed after
  6253. * creation.
  6254. * @instance
  6255. *
  6256. * @readonly
  6257. */
  6258. default: {
  6259. get: function get() {
  6260. return default_;
  6261. },
  6262. set: function set() {}
  6263. },
  6264. /**
  6265. * @memberof TextTrack
  6266. * @member {string} mode
  6267. * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
  6268. * not be set if setting to an invalid mode.
  6269. * @instance
  6270. *
  6271. * @fires TextTrack#modechange
  6272. */
  6273. mode: {
  6274. get: function get() {
  6275. return mode;
  6276. },
  6277. set: function set(newMode) {
  6278. var _this2 = this;
  6279. if (!TextTrackMode[newMode]) {
  6280. return;
  6281. }
  6282. mode = newMode;
  6283. if (mode !== 'disabled') {
  6284. this.tech_.ready(function () {
  6285. _this2.tech_.on('timeupdate', timeupdateHandler);
  6286. }, true);
  6287. } else {
  6288. this.tech_.off('timeupdate', timeupdateHandler);
  6289. }
  6290. /**
  6291. * An event that fires when mode changes on this track. This allows
  6292. * the TextTrackList that holds this track to act accordingly.
  6293. *
  6294. * > Note: This is not part of the spec!
  6295. *
  6296. * @event TextTrack#modechange
  6297. * @type {EventTarget~Event}
  6298. */
  6299. this.trigger('modechange');
  6300. }
  6301. },
  6302. /**
  6303. * @memberof TextTrack
  6304. * @member {TextTrackCueList} cues
  6305. * The text track cue list for this TextTrack.
  6306. * @instance
  6307. */
  6308. cues: {
  6309. get: function get() {
  6310. if (!this.loaded_) {
  6311. return null;
  6312. }
  6313. return cues;
  6314. },
  6315. set: function set() {}
  6316. },
  6317. /**
  6318. * @memberof TextTrack
  6319. * @member {TextTrackCueList} activeCues
  6320. * The list text track cues that are currently active for this TextTrack.
  6321. * @instance
  6322. */
  6323. activeCues: {
  6324. get: function get() {
  6325. if (!this.loaded_) {
  6326. return null;
  6327. } // nothing to do
  6328. if (this.cues.length === 0) {
  6329. return activeCues;
  6330. }
  6331. var ct = this.tech_.currentTime();
  6332. var active = [];
  6333. for (var i = 0, l = this.cues.length; i < l; i++) {
  6334. var cue = this.cues[i];
  6335. if (cue.startTime <= ct && cue.endTime >= ct) {
  6336. active.push(cue);
  6337. } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
  6338. active.push(cue);
  6339. }
  6340. }
  6341. changed = false;
  6342. if (active.length !== this.activeCues_.length) {
  6343. changed = true;
  6344. } else {
  6345. for (var _i = 0; _i < active.length; _i++) {
  6346. if (this.activeCues_.indexOf(active[_i]) === -1) {
  6347. changed = true;
  6348. }
  6349. }
  6350. }
  6351. this.activeCues_ = active;
  6352. activeCues.setCues_(this.activeCues_);
  6353. return activeCues;
  6354. },
  6355. // /!\ Keep this setter empty (see the timeupdate handler above)
  6356. set: function set() {}
  6357. }
  6358. });
  6359. if (settings.src) {
  6360. _this.src = settings.src;
  6361. loadTrack(settings.src, _assertThisInitialized(_assertThisInitialized(_this)));
  6362. } else {
  6363. _this.loaded_ = true;
  6364. }
  6365. return _this;
  6366. }
  6367. /**
  6368. * Add a cue to the internal list of cues.
  6369. *
  6370. * @param {TextTrack~Cue} cue
  6371. * The cue to add to our internal list
  6372. */
  6373. var _proto = TextTrack.prototype;
  6374. _proto.addCue = function addCue(originalCue) {
  6375. var cue = originalCue;
  6376. if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
  6377. cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
  6378. for (var prop in originalCue) {
  6379. if (!(prop in cue)) {
  6380. cue[prop] = originalCue[prop];
  6381. }
  6382. } // make sure that `id` is copied over
  6383. cue.id = originalCue.id;
  6384. cue.originalCue_ = originalCue;
  6385. }
  6386. var tracks = this.tech_.textTracks();
  6387. for (var i = 0; i < tracks.length; i++) {
  6388. if (tracks[i] !== this) {
  6389. tracks[i].removeCue(cue);
  6390. }
  6391. }
  6392. this.cues_.push(cue);
  6393. this.cues.setCues_(this.cues_);
  6394. }
  6395. /**
  6396. * Remove a cue from our internal list
  6397. *
  6398. * @param {TextTrack~Cue} removeCue
  6399. * The cue to remove from our internal list
  6400. */
  6401. ;
  6402. _proto.removeCue = function removeCue(_removeCue) {
  6403. var i = this.cues_.length;
  6404. while (i--) {
  6405. var cue = this.cues_[i];
  6406. if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
  6407. this.cues_.splice(i, 1);
  6408. this.cues.setCues_(this.cues_);
  6409. break;
  6410. }
  6411. }
  6412. };
  6413. return TextTrack;
  6414. }(Track);
  6415. /**
  6416. * cuechange - One or more cues in the track have become active or stopped being active.
  6417. */
  6418. TextTrack.prototype.allowedEvents_ = {
  6419. cuechange: 'cuechange'
  6420. };
  6421. /**
  6422. * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
  6423. * only one `AudioTrack` in the list will be enabled at a time.
  6424. *
  6425. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
  6426. * @extends Track
  6427. */
  6428. var AudioTrack =
  6429. /*#__PURE__*/
  6430. function (_Track) {
  6431. _inheritsLoose(AudioTrack, _Track);
  6432. /**
  6433. * Create an instance of this class.
  6434. *
  6435. * @param {Object} [options={}]
  6436. * Object of option names and values
  6437. *
  6438. * @param {AudioTrack~Kind} [options.kind='']
  6439. * A valid audio track kind
  6440. *
  6441. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6442. * A unique id for this AudioTrack.
  6443. *
  6444. * @param {string} [options.label='']
  6445. * The menu label for this track.
  6446. *
  6447. * @param {string} [options.language='']
  6448. * A valid two character language code.
  6449. *
  6450. * @param {boolean} [options.enabled]
  6451. * If this track is the one that is currently playing. If this track is part of
  6452. * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
  6453. */
  6454. function AudioTrack(options) {
  6455. var _this;
  6456. if (options === void 0) {
  6457. options = {};
  6458. }
  6459. var settings = mergeOptions(options, {
  6460. kind: AudioTrackKind[options.kind] || ''
  6461. });
  6462. _this = _Track.call(this, settings) || this;
  6463. var enabled = false;
  6464. /**
  6465. * @memberof AudioTrack
  6466. * @member {boolean} enabled
  6467. * If this `AudioTrack` is enabled or not. When setting this will
  6468. * fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
  6469. * @instance
  6470. *
  6471. * @fires VideoTrack#selectedchange
  6472. */
  6473. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'enabled', {
  6474. get: function get() {
  6475. return enabled;
  6476. },
  6477. set: function set(newEnabled) {
  6478. // an invalid or unchanged value
  6479. if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
  6480. return;
  6481. }
  6482. enabled = newEnabled;
  6483. /**
  6484. * An event that fires when enabled changes on this track. This allows
  6485. * the AudioTrackList that holds this track to act accordingly.
  6486. *
  6487. * > Note: This is not part of the spec! Native tracks will do
  6488. * this internally without an event.
  6489. *
  6490. * @event AudioTrack#enabledchange
  6491. * @type {EventTarget~Event}
  6492. */
  6493. this.trigger('enabledchange');
  6494. }
  6495. }); // if the user sets this track to selected then
  6496. // set selected to that true value otherwise
  6497. // we keep it false
  6498. if (settings.enabled) {
  6499. _this.enabled = settings.enabled;
  6500. }
  6501. _this.loaded_ = true;
  6502. return _this;
  6503. }
  6504. return AudioTrack;
  6505. }(Track);
  6506. /**
  6507. * A representation of a single `VideoTrack`.
  6508. *
  6509. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
  6510. * @extends Track
  6511. */
  6512. var VideoTrack =
  6513. /*#__PURE__*/
  6514. function (_Track) {
  6515. _inheritsLoose(VideoTrack, _Track);
  6516. /**
  6517. * Create an instance of this class.
  6518. *
  6519. * @param {Object} [options={}]
  6520. * Object of option names and values
  6521. *
  6522. * @param {string} [options.kind='']
  6523. * A valid {@link VideoTrack~Kind}
  6524. *
  6525. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6526. * A unique id for this AudioTrack.
  6527. *
  6528. * @param {string} [options.label='']
  6529. * The menu label for this track.
  6530. *
  6531. * @param {string} [options.language='']
  6532. * A valid two character language code.
  6533. *
  6534. * @param {boolean} [options.selected]
  6535. * If this track is the one that is currently playing.
  6536. */
  6537. function VideoTrack(options) {
  6538. var _this;
  6539. if (options === void 0) {
  6540. options = {};
  6541. }
  6542. var settings = mergeOptions(options, {
  6543. kind: VideoTrackKind[options.kind] || ''
  6544. });
  6545. _this = _Track.call(this, settings) || this;
  6546. var selected = false;
  6547. /**
  6548. * @memberof VideoTrack
  6549. * @member {boolean} selected
  6550. * If this `VideoTrack` is selected or not. When setting this will
  6551. * fire {@link VideoTrack#selectedchange} if the state of selected changed.
  6552. * @instance
  6553. *
  6554. * @fires VideoTrack#selectedchange
  6555. */
  6556. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selected', {
  6557. get: function get() {
  6558. return selected;
  6559. },
  6560. set: function set(newSelected) {
  6561. // an invalid or unchanged value
  6562. if (typeof newSelected !== 'boolean' || newSelected === selected) {
  6563. return;
  6564. }
  6565. selected = newSelected;
  6566. /**
  6567. * An event that fires when selected changes on this track. This allows
  6568. * the VideoTrackList that holds this track to act accordingly.
  6569. *
  6570. * > Note: This is not part of the spec! Native tracks will do
  6571. * this internally without an event.
  6572. *
  6573. * @event VideoTrack#selectedchange
  6574. * @type {EventTarget~Event}
  6575. */
  6576. this.trigger('selectedchange');
  6577. }
  6578. }); // if the user sets this track to selected then
  6579. // set selected to that true value otherwise
  6580. // we keep it false
  6581. if (settings.selected) {
  6582. _this.selected = settings.selected;
  6583. }
  6584. return _this;
  6585. }
  6586. return VideoTrack;
  6587. }(Track);
  6588. /**
  6589. * @memberof HTMLTrackElement
  6590. * @typedef {HTMLTrackElement~ReadyState}
  6591. * @enum {number}
  6592. */
  6593. var NONE = 0;
  6594. var LOADING = 1;
  6595. var LOADED = 2;
  6596. var ERROR = 3;
  6597. /**
  6598. * A single track represented in the DOM.
  6599. *
  6600. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
  6601. * @extends EventTarget
  6602. */
  6603. var HTMLTrackElement =
  6604. /*#__PURE__*/
  6605. function (_EventTarget) {
  6606. _inheritsLoose(HTMLTrackElement, _EventTarget);
  6607. /**
  6608. * Create an instance of this class.
  6609. *
  6610. * @param {Object} options={}
  6611. * Object of option names and values
  6612. *
  6613. * @param {Tech} options.tech
  6614. * A reference to the tech that owns this HTMLTrackElement.
  6615. *
  6616. * @param {TextTrack~Kind} [options.kind='subtitles']
  6617. * A valid text track kind.
  6618. *
  6619. * @param {TextTrack~Mode} [options.mode='disabled']
  6620. * A valid text track mode.
  6621. *
  6622. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6623. * A unique id for this TextTrack.
  6624. *
  6625. * @param {string} [options.label='']
  6626. * The menu label for this track.
  6627. *
  6628. * @param {string} [options.language='']
  6629. * A valid two character language code.
  6630. *
  6631. * @param {string} [options.srclang='']
  6632. * A valid two character language code. An alternative, but deprioritized
  6633. * vesion of `options.language`
  6634. *
  6635. * @param {string} [options.src]
  6636. * A url to TextTrack cues.
  6637. *
  6638. * @param {boolean} [options.default]
  6639. * If this track should default to on or off.
  6640. */
  6641. function HTMLTrackElement(options) {
  6642. var _this;
  6643. if (options === void 0) {
  6644. options = {};
  6645. }
  6646. _this = _EventTarget.call(this) || this;
  6647. var readyState;
  6648. var track = new TextTrack(options);
  6649. _this.kind = track.kind;
  6650. _this.src = track.src;
  6651. _this.srclang = track.language;
  6652. _this.label = track.label;
  6653. _this.default = track.default;
  6654. Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
  6655. /**
  6656. * @memberof HTMLTrackElement
  6657. * @member {HTMLTrackElement~ReadyState} readyState
  6658. * The current ready state of the track element.
  6659. * @instance
  6660. */
  6661. readyState: {
  6662. get: function get() {
  6663. return readyState;
  6664. }
  6665. },
  6666. /**
  6667. * @memberof HTMLTrackElement
  6668. * @member {TextTrack} track
  6669. * The underlying TextTrack object.
  6670. * @instance
  6671. *
  6672. */
  6673. track: {
  6674. get: function get() {
  6675. return track;
  6676. }
  6677. }
  6678. });
  6679. readyState = NONE;
  6680. /**
  6681. * @listens TextTrack#loadeddata
  6682. * @fires HTMLTrackElement#load
  6683. */
  6684. track.addEventListener('loadeddata', function () {
  6685. readyState = LOADED;
  6686. _this.trigger({
  6687. type: 'load',
  6688. target: _assertThisInitialized(_assertThisInitialized(_this))
  6689. });
  6690. });
  6691. return _this;
  6692. }
  6693. return HTMLTrackElement;
  6694. }(EventTarget);
  6695. HTMLTrackElement.prototype.allowedEvents_ = {
  6696. load: 'load'
  6697. };
  6698. HTMLTrackElement.NONE = NONE;
  6699. HTMLTrackElement.LOADING = LOADING;
  6700. HTMLTrackElement.LOADED = LOADED;
  6701. HTMLTrackElement.ERROR = ERROR;
  6702. /*
  6703. * This file contains all track properties that are used in
  6704. * player.js, tech.js, html5.js and possibly other techs in the future.
  6705. */
  6706. var NORMAL = {
  6707. audio: {
  6708. ListClass: AudioTrackList,
  6709. TrackClass: AudioTrack,
  6710. capitalName: 'Audio'
  6711. },
  6712. video: {
  6713. ListClass: VideoTrackList,
  6714. TrackClass: VideoTrack,
  6715. capitalName: 'Video'
  6716. },
  6717. text: {
  6718. ListClass: TextTrackList,
  6719. TrackClass: TextTrack,
  6720. capitalName: 'Text'
  6721. }
  6722. };
  6723. Object.keys(NORMAL).forEach(function (type) {
  6724. NORMAL[type].getterName = type + "Tracks";
  6725. NORMAL[type].privateName = type + "Tracks_";
  6726. });
  6727. var REMOTE = {
  6728. remoteText: {
  6729. ListClass: TextTrackList,
  6730. TrackClass: TextTrack,
  6731. capitalName: 'RemoteText',
  6732. getterName: 'remoteTextTracks',
  6733. privateName: 'remoteTextTracks_'
  6734. },
  6735. remoteTextEl: {
  6736. ListClass: HtmlTrackElementList,
  6737. TrackClass: HTMLTrackElement,
  6738. capitalName: 'RemoteTextTrackEls',
  6739. getterName: 'remoteTextTrackEls',
  6740. privateName: 'remoteTextTrackEls_'
  6741. }
  6742. };
  6743. var ALL = mergeOptions(NORMAL, REMOTE);
  6744. REMOTE.names = Object.keys(REMOTE);
  6745. NORMAL.names = Object.keys(NORMAL);
  6746. ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
  6747. /**
  6748. * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
  6749. * that just contains the src url alone.
  6750. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
  6751. * `var SourceString = 'http://example.com/some-video.mp4';`
  6752. *
  6753. * @typedef {Object|string} Tech~SourceObject
  6754. *
  6755. * @property {string} src
  6756. * The url to the source
  6757. *
  6758. * @property {string} type
  6759. * The mime type of the source
  6760. */
  6761. /**
  6762. * A function used by {@link Tech} to create a new {@link TextTrack}.
  6763. *
  6764. * @private
  6765. *
  6766. * @param {Tech} self
  6767. * An instance of the Tech class.
  6768. *
  6769. * @param {string} kind
  6770. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  6771. *
  6772. * @param {string} [label]
  6773. * Label to identify the text track
  6774. *
  6775. * @param {string} [language]
  6776. * Two letter language abbreviation
  6777. *
  6778. * @param {Object} [options={}]
  6779. * An object with additional text track options
  6780. *
  6781. * @return {TextTrack}
  6782. * The text track that was created.
  6783. */
  6784. function createTrackHelper(self, kind, label, language, options) {
  6785. if (options === void 0) {
  6786. options = {};
  6787. }
  6788. var tracks = self.textTracks();
  6789. options.kind = kind;
  6790. if (label) {
  6791. options.label = label;
  6792. }
  6793. if (language) {
  6794. options.language = language;
  6795. }
  6796. options.tech = self;
  6797. var track = new ALL.text.TrackClass(options);
  6798. tracks.addTrack(track);
  6799. return track;
  6800. }
  6801. /**
  6802. * This is the base class for media playback technology controllers, such as
  6803. * {@link Flash} and {@link HTML5}
  6804. *
  6805. * @extends Component
  6806. */
  6807. var Tech =
  6808. /*#__PURE__*/
  6809. function (_Component) {
  6810. _inheritsLoose(Tech, _Component);
  6811. /**
  6812. * Create an instance of this Tech.
  6813. *
  6814. * @param {Object} [options]
  6815. * The key/value store of player options.
  6816. *
  6817. * @param {Component~ReadyCallback} ready
  6818. * Callback function to call when the `HTML5` Tech is ready.
  6819. */
  6820. function Tech(options, ready) {
  6821. var _this;
  6822. if (options === void 0) {
  6823. options = {};
  6824. }
  6825. if (ready === void 0) {
  6826. ready = function ready() {};
  6827. }
  6828. // we don't want the tech to report user activity automatically.
  6829. // This is done manually in addControlsListeners
  6830. options.reportTouchActivity = false;
  6831. _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to
  6832. // implement a very limited played()
  6833. _this.hasStarted_ = false;
  6834. _this.on('playing', function () {
  6835. this.hasStarted_ = true;
  6836. });
  6837. _this.on('loadstart', function () {
  6838. this.hasStarted_ = false;
  6839. });
  6840. ALL.names.forEach(function (name) {
  6841. var props = ALL[name];
  6842. if (options && options[props.getterName]) {
  6843. _this[props.privateName] = options[props.getterName];
  6844. }
  6845. }); // Manually track progress in cases where the browser/flash player doesn't report it.
  6846. if (!_this.featuresProgressEvents) {
  6847. _this.manualProgressOn();
  6848. } // Manually track timeupdates in cases where the browser/flash player doesn't report it.
  6849. if (!_this.featuresTimeupdateEvents) {
  6850. _this.manualTimeUpdatesOn();
  6851. }
  6852. ['Text', 'Audio', 'Video'].forEach(function (track) {
  6853. if (options["native" + track + "Tracks"] === false) {
  6854. _this["featuresNative" + track + "Tracks"] = false;
  6855. }
  6856. });
  6857. if (options.nativeCaptions === false || options.nativeTextTracks === false) {
  6858. _this.featuresNativeTextTracks = false;
  6859. } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
  6860. _this.featuresNativeTextTracks = true;
  6861. }
  6862. if (!_this.featuresNativeTextTracks) {
  6863. _this.emulateTextTracks();
  6864. }
  6865. _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
  6866. _this.initTrackListeners(); // Turn on component tap events only if not using native controls
  6867. if (!options.nativeControlsForTouch) {
  6868. _this.emitTapEvents();
  6869. }
  6870. if (_this.constructor) {
  6871. _this.name_ = _this.constructor.name || 'Unknown Tech';
  6872. }
  6873. return _this;
  6874. }
  6875. /**
  6876. * A special function to trigger source set in a way that will allow player
  6877. * to re-trigger if the player or tech are not ready yet.
  6878. *
  6879. * @fires Tech#sourceset
  6880. * @param {string} src The source string at the time of the source changing.
  6881. */
  6882. var _proto = Tech.prototype;
  6883. _proto.triggerSourceset = function triggerSourceset(src) {
  6884. var _this2 = this;
  6885. if (!this.isReady_) {
  6886. // on initial ready we have to trigger source set
  6887. // 1ms after ready so that player can watch for it.
  6888. this.one('ready', function () {
  6889. return _this2.setTimeout(function () {
  6890. return _this2.triggerSourceset(src);
  6891. }, 1);
  6892. });
  6893. }
  6894. /**
  6895. * Fired when the source is set on the tech causing the media element
  6896. * to reload.
  6897. *
  6898. * @see {@link Player#event:sourceset}
  6899. * @event Tech#sourceset
  6900. * @type {EventTarget~Event}
  6901. */
  6902. this.trigger({
  6903. src: src,
  6904. type: 'sourceset'
  6905. });
  6906. }
  6907. /* Fallbacks for unsupported event types
  6908. ================================================================================ */
  6909. /**
  6910. * Polyfill the `progress` event for browsers that don't support it natively.
  6911. *
  6912. * @see {@link Tech#trackProgress}
  6913. */
  6914. ;
  6915. _proto.manualProgressOn = function manualProgressOn() {
  6916. this.on('durationchange', this.onDurationChange);
  6917. this.manualProgress = true; // Trigger progress watching when a source begins loading
  6918. this.one('ready', this.trackProgress);
  6919. }
  6920. /**
  6921. * Turn off the polyfill for `progress` events that was created in
  6922. * {@link Tech#manualProgressOn}
  6923. */
  6924. ;
  6925. _proto.manualProgressOff = function manualProgressOff() {
  6926. this.manualProgress = false;
  6927. this.stopTrackingProgress();
  6928. this.off('durationchange', this.onDurationChange);
  6929. }
  6930. /**
  6931. * This is used to trigger a `progress` event when the buffered percent changes. It
  6932. * sets an interval function that will be called every 500 milliseconds to check if the
  6933. * buffer end percent has changed.
  6934. *
  6935. * > This function is called by {@link Tech#manualProgressOn}
  6936. *
  6937. * @param {EventTarget~Event} event
  6938. * The `ready` event that caused this to run.
  6939. *
  6940. * @listens Tech#ready
  6941. * @fires Tech#progress
  6942. */
  6943. ;
  6944. _proto.trackProgress = function trackProgress(event) {
  6945. this.stopTrackingProgress();
  6946. this.progressInterval = this.setInterval(bind(this, function () {
  6947. // Don't trigger unless buffered amount is greater than last time
  6948. var numBufferedPercent = this.bufferedPercent();
  6949. if (this.bufferedPercent_ !== numBufferedPercent) {
  6950. /**
  6951. * See {@link Player#progress}
  6952. *
  6953. * @event Tech#progress
  6954. * @type {EventTarget~Event}
  6955. */
  6956. this.trigger('progress');
  6957. }
  6958. this.bufferedPercent_ = numBufferedPercent;
  6959. if (numBufferedPercent === 1) {
  6960. this.stopTrackingProgress();
  6961. }
  6962. }), 500);
  6963. }
  6964. /**
  6965. * Update our internal duration on a `durationchange` event by calling
  6966. * {@link Tech#duration}.
  6967. *
  6968. * @param {EventTarget~Event} event
  6969. * The `durationchange` event that caused this to run.
  6970. *
  6971. * @listens Tech#durationchange
  6972. */
  6973. ;
  6974. _proto.onDurationChange = function onDurationChange(event) {
  6975. this.duration_ = this.duration();
  6976. }
  6977. /**
  6978. * Get and create a `TimeRange` object for buffering.
  6979. *
  6980. * @return {TimeRange}
  6981. * The time range object that was created.
  6982. */
  6983. ;
  6984. _proto.buffered = function buffered() {
  6985. return createTimeRanges(0, 0);
  6986. }
  6987. /**
  6988. * Get the percentage of the current video that is currently buffered.
  6989. *
  6990. * @return {number}
  6991. * A number from 0 to 1 that represents the decimal percentage of the
  6992. * video that is buffered.
  6993. *
  6994. */
  6995. ;
  6996. _proto.bufferedPercent = function bufferedPercent$$1() {
  6997. return bufferedPercent(this.buffered(), this.duration_);
  6998. }
  6999. /**
  7000. * Turn off the polyfill for `progress` events that was created in
  7001. * {@link Tech#manualProgressOn}
  7002. * Stop manually tracking progress events by clearing the interval that was set in
  7003. * {@link Tech#trackProgress}.
  7004. */
  7005. ;
  7006. _proto.stopTrackingProgress = function stopTrackingProgress() {
  7007. this.clearInterval(this.progressInterval);
  7008. }
  7009. /**
  7010. * Polyfill the `timeupdate` event for browsers that don't support it.
  7011. *
  7012. * @see {@link Tech#trackCurrentTime}
  7013. */
  7014. ;
  7015. _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
  7016. this.manualTimeUpdates = true;
  7017. this.on('play', this.trackCurrentTime);
  7018. this.on('pause', this.stopTrackingCurrentTime);
  7019. }
  7020. /**
  7021. * Turn off the polyfill for `timeupdate` events that was created in
  7022. * {@link Tech#manualTimeUpdatesOn}
  7023. */
  7024. ;
  7025. _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
  7026. this.manualTimeUpdates = false;
  7027. this.stopTrackingCurrentTime();
  7028. this.off('play', this.trackCurrentTime);
  7029. this.off('pause', this.stopTrackingCurrentTime);
  7030. }
  7031. /**
  7032. * Sets up an interval function to track current time and trigger `timeupdate` every
  7033. * 250 milliseconds.
  7034. *
  7035. * @listens Tech#play
  7036. * @triggers Tech#timeupdate
  7037. */
  7038. ;
  7039. _proto.trackCurrentTime = function trackCurrentTime() {
  7040. if (this.currentTimeInterval) {
  7041. this.stopTrackingCurrentTime();
  7042. }
  7043. this.currentTimeInterval = this.setInterval(function () {
  7044. /**
  7045. * Triggered at an interval of 250ms to indicated that time is passing in the video.
  7046. *
  7047. * @event Tech#timeupdate
  7048. * @type {EventTarget~Event}
  7049. */
  7050. this.trigger({
  7051. type: 'timeupdate',
  7052. target: this,
  7053. manuallyTriggered: true
  7054. }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  7055. }, 250);
  7056. }
  7057. /**
  7058. * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
  7059. * `timeupdate` event is no longer triggered.
  7060. *
  7061. * @listens {Tech#pause}
  7062. */
  7063. ;
  7064. _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
  7065. this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
  7066. // the progress bar won't make it all the way to the end
  7067. this.trigger({
  7068. type: 'timeupdate',
  7069. target: this,
  7070. manuallyTriggered: true
  7071. });
  7072. }
  7073. /**
  7074. * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
  7075. * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
  7076. *
  7077. * @fires Component#dispose
  7078. */
  7079. ;
  7080. _proto.dispose = function dispose() {
  7081. // clear out all tracks because we can't reuse them between techs
  7082. this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
  7083. if (this.manualProgress) {
  7084. this.manualProgressOff();
  7085. }
  7086. if (this.manualTimeUpdates) {
  7087. this.manualTimeUpdatesOff();
  7088. }
  7089. _Component.prototype.dispose.call(this);
  7090. }
  7091. /**
  7092. * Clear out a single `TrackList` or an array of `TrackLists` given their names.
  7093. *
  7094. * > Note: Techs without source handlers should call this between sources for `video`
  7095. * & `audio` tracks. You don't want to use them between tracks!
  7096. *
  7097. * @param {string[]|string} types
  7098. * TrackList names to clear, valid names are `video`, `audio`, and
  7099. * `text`.
  7100. */
  7101. ;
  7102. _proto.clearTracks = function clearTracks(types) {
  7103. var _this3 = this;
  7104. types = [].concat(types); // clear out all tracks because we can't reuse them between techs
  7105. types.forEach(function (type) {
  7106. var list = _this3[type + "Tracks"]() || [];
  7107. var i = list.length;
  7108. while (i--) {
  7109. var track = list[i];
  7110. if (type === 'text') {
  7111. _this3.removeRemoteTextTrack(track);
  7112. }
  7113. list.removeTrack(track);
  7114. }
  7115. });
  7116. }
  7117. /**
  7118. * Remove any TextTracks added via addRemoteTextTrack that are
  7119. * flagged for automatic garbage collection
  7120. */
  7121. ;
  7122. _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
  7123. var list = this.autoRemoteTextTracks_ || [];
  7124. var i = list.length;
  7125. while (i--) {
  7126. var track = list[i];
  7127. this.removeRemoteTextTrack(track);
  7128. }
  7129. }
  7130. /**
  7131. * Reset the tech, which will removes all sources and reset the internal readyState.
  7132. *
  7133. * @abstract
  7134. */
  7135. ;
  7136. _proto.reset = function reset() {}
  7137. /**
  7138. * Get or set an error on the Tech.
  7139. *
  7140. * @param {MediaError} [err]
  7141. * Error to set on the Tech
  7142. *
  7143. * @return {MediaError|null}
  7144. * The current error object on the tech, or null if there isn't one.
  7145. */
  7146. ;
  7147. _proto.error = function error(err) {
  7148. if (err !== undefined) {
  7149. this.error_ = new MediaError(err);
  7150. this.trigger('error');
  7151. }
  7152. return this.error_;
  7153. }
  7154. /**
  7155. * Returns the `TimeRange`s that have been played through for the current source.
  7156. *
  7157. * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
  7158. * It only checks whether the source has played at all or not.
  7159. *
  7160. * @return {TimeRange}
  7161. * - A single time range if this video has played
  7162. * - An empty set of ranges if not.
  7163. */
  7164. ;
  7165. _proto.played = function played() {
  7166. if (this.hasStarted_) {
  7167. return createTimeRanges(0, 0);
  7168. }
  7169. return createTimeRanges();
  7170. }
  7171. /**
  7172. * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
  7173. * previously called.
  7174. *
  7175. * @fires Tech#timeupdate
  7176. */
  7177. ;
  7178. _proto.setCurrentTime = function setCurrentTime() {
  7179. // improve the accuracy of manual timeupdates
  7180. if (this.manualTimeUpdates) {
  7181. /**
  7182. * A manual `timeupdate` event.
  7183. *
  7184. * @event Tech#timeupdate
  7185. * @type {EventTarget~Event}
  7186. */
  7187. this.trigger({
  7188. type: 'timeupdate',
  7189. target: this,
  7190. manuallyTriggered: true
  7191. });
  7192. }
  7193. }
  7194. /**
  7195. * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
  7196. * {@link TextTrackList} events.
  7197. *
  7198. * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
  7199. *
  7200. * @fires Tech#audiotrackchange
  7201. * @fires Tech#videotrackchange
  7202. * @fires Tech#texttrackchange
  7203. */
  7204. ;
  7205. _proto.initTrackListeners = function initTrackListeners() {
  7206. var _this4 = this;
  7207. /**
  7208. * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
  7209. *
  7210. * @event Tech#audiotrackchange
  7211. * @type {EventTarget~Event}
  7212. */
  7213. /**
  7214. * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
  7215. *
  7216. * @event Tech#videotrackchange
  7217. * @type {EventTarget~Event}
  7218. */
  7219. /**
  7220. * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
  7221. *
  7222. * @event Tech#texttrackchange
  7223. * @type {EventTarget~Event}
  7224. */
  7225. NORMAL.names.forEach(function (name) {
  7226. var props = NORMAL[name];
  7227. var trackListChanges = function trackListChanges() {
  7228. _this4.trigger(name + "trackchange");
  7229. };
  7230. var tracks = _this4[props.getterName]();
  7231. tracks.addEventListener('removetrack', trackListChanges);
  7232. tracks.addEventListener('addtrack', trackListChanges);
  7233. _this4.on('dispose', function () {
  7234. tracks.removeEventListener('removetrack', trackListChanges);
  7235. tracks.removeEventListener('addtrack', trackListChanges);
  7236. });
  7237. });
  7238. }
  7239. /**
  7240. * Emulate TextTracks using vtt.js if necessary
  7241. *
  7242. * @fires Tech#vttjsloaded
  7243. * @fires Tech#vttjserror
  7244. */
  7245. ;
  7246. _proto.addWebVttScript_ = function addWebVttScript_() {
  7247. var _this5 = this;
  7248. if (window$1.WebVTT) {
  7249. return;
  7250. } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
  7251. // signals that the Tech is ready at which point Tech.el_ is part of the DOM
  7252. // before inserting the WebVTT script
  7253. if (document.body.contains(this.el())) {
  7254. // load via require if available and vtt.js script location was not passed in
  7255. // as an option. novtt builds will turn the above require call into an empty object
  7256. // which will cause this if check to always fail.
  7257. if (!this.options_['vtt.js'] && isPlain(vtt) && Object.keys(vtt).length > 0) {
  7258. this.trigger('vttjsloaded');
  7259. return;
  7260. } // load vtt.js via the script location option or the cdn of no location was
  7261. // passed in
  7262. var script = document.createElement('script');
  7263. script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
  7264. script.onload = function () {
  7265. /**
  7266. * Fired when vtt.js is loaded.
  7267. *
  7268. * @event Tech#vttjsloaded
  7269. * @type {EventTarget~Event}
  7270. */
  7271. _this5.trigger('vttjsloaded');
  7272. };
  7273. script.onerror = function () {
  7274. /**
  7275. * Fired when vtt.js was not loaded due to an error
  7276. *
  7277. * @event Tech#vttjsloaded
  7278. * @type {EventTarget~Event}
  7279. */
  7280. _this5.trigger('vttjserror');
  7281. };
  7282. this.on('dispose', function () {
  7283. script.onload = null;
  7284. script.onerror = null;
  7285. }); // but have not loaded yet and we set it to true before the inject so that
  7286. // we don't overwrite the injected window.WebVTT if it loads right away
  7287. window$1.WebVTT = true;
  7288. this.el().parentNode.appendChild(script);
  7289. } else {
  7290. this.ready(this.addWebVttScript_);
  7291. }
  7292. }
  7293. /**
  7294. * Emulate texttracks
  7295. *
  7296. */
  7297. ;
  7298. _proto.emulateTextTracks = function emulateTextTracks() {
  7299. var _this6 = this;
  7300. var tracks = this.textTracks();
  7301. var remoteTracks = this.remoteTextTracks();
  7302. var handleAddTrack = function handleAddTrack(e) {
  7303. return tracks.addTrack(e.track);
  7304. };
  7305. var handleRemoveTrack = function handleRemoveTrack(e) {
  7306. return tracks.removeTrack(e.track);
  7307. };
  7308. remoteTracks.on('addtrack', handleAddTrack);
  7309. remoteTracks.on('removetrack', handleRemoveTrack);
  7310. this.addWebVttScript_();
  7311. var updateDisplay = function updateDisplay() {
  7312. return _this6.trigger('texttrackchange');
  7313. };
  7314. var textTracksChanges = function textTracksChanges() {
  7315. updateDisplay();
  7316. for (var i = 0; i < tracks.length; i++) {
  7317. var track = tracks[i];
  7318. track.removeEventListener('cuechange', updateDisplay);
  7319. if (track.mode === 'showing') {
  7320. track.addEventListener('cuechange', updateDisplay);
  7321. }
  7322. }
  7323. };
  7324. textTracksChanges();
  7325. tracks.addEventListener('change', textTracksChanges);
  7326. tracks.addEventListener('addtrack', textTracksChanges);
  7327. tracks.addEventListener('removetrack', textTracksChanges);
  7328. this.on('dispose', function () {
  7329. remoteTracks.off('addtrack', handleAddTrack);
  7330. remoteTracks.off('removetrack', handleRemoveTrack);
  7331. tracks.removeEventListener('change', textTracksChanges);
  7332. tracks.removeEventListener('addtrack', textTracksChanges);
  7333. tracks.removeEventListener('removetrack', textTracksChanges);
  7334. for (var i = 0; i < tracks.length; i++) {
  7335. var track = tracks[i];
  7336. track.removeEventListener('cuechange', updateDisplay);
  7337. }
  7338. });
  7339. }
  7340. /**
  7341. * Create and returns a remote {@link TextTrack} object.
  7342. *
  7343. * @param {string} kind
  7344. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  7345. *
  7346. * @param {string} [label]
  7347. * Label to identify the text track
  7348. *
  7349. * @param {string} [language]
  7350. * Two letter language abbreviation
  7351. *
  7352. * @return {TextTrack}
  7353. * The TextTrack that gets created.
  7354. */
  7355. ;
  7356. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  7357. if (!kind) {
  7358. throw new Error('TextTrack kind is required but was not provided');
  7359. }
  7360. return createTrackHelper(this, kind, label, language);
  7361. }
  7362. /**
  7363. * Create an emulated TextTrack for use by addRemoteTextTrack
  7364. *
  7365. * This is intended to be overridden by classes that inherit from
  7366. * Tech in order to create native or custom TextTracks.
  7367. *
  7368. * @param {Object} options
  7369. * The object should contain the options to initialize the TextTrack with.
  7370. *
  7371. * @param {string} [options.kind]
  7372. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  7373. *
  7374. * @param {string} [options.label].
  7375. * Label to identify the text track
  7376. *
  7377. * @param {string} [options.language]
  7378. * Two letter language abbreviation.
  7379. *
  7380. * @return {HTMLTrackElement}
  7381. * The track element that gets created.
  7382. */
  7383. ;
  7384. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  7385. var track = mergeOptions(options, {
  7386. tech: this
  7387. });
  7388. return new REMOTE.remoteTextEl.TrackClass(track);
  7389. }
  7390. /**
  7391. * Creates a remote text track object and returns an html track element.
  7392. *
  7393. * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
  7394. *
  7395. * @param {Object} options
  7396. * See {@link Tech#createRemoteTextTrack} for more detailed properties.
  7397. *
  7398. * @param {boolean} [manualCleanup=true]
  7399. * - When false: the TextTrack will be automatically removed from the video
  7400. * element whenever the source changes
  7401. * - When True: The TextTrack will have to be cleaned up manually
  7402. *
  7403. * @return {HTMLTrackElement}
  7404. * An Html Track Element.
  7405. *
  7406. * @deprecated The default functionality for this function will be equivalent
  7407. * to "manualCleanup=false" in the future. The manualCleanup parameter will
  7408. * also be removed.
  7409. */
  7410. ;
  7411. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  7412. var _this7 = this;
  7413. if (options === void 0) {
  7414. options = {};
  7415. }
  7416. var htmlTrackElement = this.createRemoteTextTrack(options);
  7417. if (manualCleanup !== true && manualCleanup !== false) {
  7418. // deprecation warning
  7419. log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
  7420. manualCleanup = true;
  7421. } // store HTMLTrackElement and TextTrack to remote list
  7422. this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
  7423. this.remoteTextTracks().addTrack(htmlTrackElement.track);
  7424. if (manualCleanup !== true) {
  7425. // create the TextTrackList if it doesn't exist
  7426. this.ready(function () {
  7427. return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
  7428. });
  7429. }
  7430. return htmlTrackElement;
  7431. }
  7432. /**
  7433. * Remove a remote text track from the remote `TextTrackList`.
  7434. *
  7435. * @param {TextTrack} track
  7436. * `TextTrack` to remove from the `TextTrackList`
  7437. */
  7438. ;
  7439. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  7440. var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
  7441. this.remoteTextTrackEls().removeTrackElement_(trackElement);
  7442. this.remoteTextTracks().removeTrack(track);
  7443. this.autoRemoteTextTracks_.removeTrack(track);
  7444. }
  7445. /**
  7446. * Gets available media playback quality metrics as specified by the W3C's Media
  7447. * Playback Quality API.
  7448. *
  7449. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  7450. *
  7451. * @return {Object}
  7452. * An object with supported media playback quality metrics
  7453. *
  7454. * @abstract
  7455. */
  7456. ;
  7457. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  7458. return {};
  7459. }
  7460. /**
  7461. * A method to set a poster from a `Tech`.
  7462. *
  7463. * @abstract
  7464. */
  7465. ;
  7466. _proto.setPoster = function setPoster() {}
  7467. /**
  7468. * A method to check for the presence of the 'playsinline' <video> attribute.
  7469. *
  7470. * @abstract
  7471. */
  7472. ;
  7473. _proto.playsinline = function playsinline() {}
  7474. /**
  7475. * A method to set or unset the 'playsinline' <video> attribute.
  7476. *
  7477. * @abstract
  7478. */
  7479. ;
  7480. _proto.setPlaysinline = function setPlaysinline() {}
  7481. /**
  7482. * Attempt to force override of native audio tracks.
  7483. *
  7484. * @param {boolean} override - If set to true native audio will be overridden,
  7485. * otherwise native audio will potentially be used.
  7486. *
  7487. * @abstract
  7488. */
  7489. ;
  7490. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
  7491. /**
  7492. * Attempt to force override of native video tracks.
  7493. *
  7494. * @param {boolean} override - If set to true native video will be overridden,
  7495. * otherwise native video will potentially be used.
  7496. *
  7497. * @abstract
  7498. */
  7499. ;
  7500. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
  7501. /*
  7502. * Check if the tech can support the given mime-type.
  7503. *
  7504. * The base tech does not support any type, but source handlers might
  7505. * overwrite this.
  7506. *
  7507. * @param {string} type
  7508. * The mimetype to check for support
  7509. *
  7510. * @return {string}
  7511. * 'probably', 'maybe', or empty string
  7512. *
  7513. * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
  7514. *
  7515. * @abstract
  7516. */
  7517. ;
  7518. _proto.canPlayType = function canPlayType() {
  7519. return '';
  7520. }
  7521. /**
  7522. * Check if the type is supported by this tech.
  7523. *
  7524. * The base tech does not support any type, but source handlers might
  7525. * overwrite this.
  7526. *
  7527. * @param {string} type
  7528. * The media type to check
  7529. * @return {string} Returns the native video element's response
  7530. */
  7531. ;
  7532. Tech.canPlayType = function canPlayType() {
  7533. return '';
  7534. }
  7535. /**
  7536. * Check if the tech can support the given source
  7537. *
  7538. * @param {Object} srcObj
  7539. * The source object
  7540. * @param {Object} options
  7541. * The options passed to the tech
  7542. * @return {string} 'probably', 'maybe', or '' (empty string)
  7543. */
  7544. ;
  7545. Tech.canPlaySource = function canPlaySource(srcObj, options) {
  7546. return Tech.canPlayType(srcObj.type);
  7547. }
  7548. /*
  7549. * Return whether the argument is a Tech or not.
  7550. * Can be passed either a Class like `Html5` or a instance like `player.tech_`
  7551. *
  7552. * @param {Object} component
  7553. * The item to check
  7554. *
  7555. * @return {boolean}
  7556. * Whether it is a tech or not
  7557. * - True if it is a tech
  7558. * - False if it is not
  7559. */
  7560. ;
  7561. Tech.isTech = function isTech(component) {
  7562. return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  7563. }
  7564. /**
  7565. * Registers a `Tech` into a shared list for videojs.
  7566. *
  7567. * @param {string} name
  7568. * Name of the `Tech` to register.
  7569. *
  7570. * @param {Object} tech
  7571. * The `Tech` class to register.
  7572. */
  7573. ;
  7574. Tech.registerTech = function registerTech(name, tech) {
  7575. if (!Tech.techs_) {
  7576. Tech.techs_ = {};
  7577. }
  7578. if (!Tech.isTech(tech)) {
  7579. throw new Error("Tech " + name + " must be a Tech");
  7580. }
  7581. if (!Tech.canPlayType) {
  7582. throw new Error('Techs must have a static canPlayType method on them');
  7583. }
  7584. if (!Tech.canPlaySource) {
  7585. throw new Error('Techs must have a static canPlaySource method on them');
  7586. }
  7587. name = toTitleCase(name);
  7588. Tech.techs_[name] = tech;
  7589. if (name !== 'Tech') {
  7590. // camel case the techName for use in techOrder
  7591. Tech.defaultTechOrder_.push(name);
  7592. }
  7593. return tech;
  7594. }
  7595. /**
  7596. * Get a `Tech` from the shared list by name.
  7597. *
  7598. * @param {string} name
  7599. * `camelCase` or `TitleCase` name of the Tech to get
  7600. *
  7601. * @return {Tech|undefined}
  7602. * The `Tech` or undefined if there was no tech with the name requested.
  7603. */
  7604. ;
  7605. Tech.getTech = function getTech(name) {
  7606. if (!name) {
  7607. return;
  7608. }
  7609. name = toTitleCase(name);
  7610. if (Tech.techs_ && Tech.techs_[name]) {
  7611. return Tech.techs_[name];
  7612. }
  7613. if (window$1 && window$1.videojs && window$1.videojs[name]) {
  7614. log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
  7615. return window$1.videojs[name];
  7616. }
  7617. };
  7618. return Tech;
  7619. }(Component);
  7620. /**
  7621. * Get the {@link VideoTrackList}
  7622. *
  7623. * @returns {VideoTrackList}
  7624. * @method Tech.prototype.videoTracks
  7625. */
  7626. /**
  7627. * Get the {@link AudioTrackList}
  7628. *
  7629. * @returns {AudioTrackList}
  7630. * @method Tech.prototype.audioTracks
  7631. */
  7632. /**
  7633. * Get the {@link TextTrackList}
  7634. *
  7635. * @returns {TextTrackList}
  7636. * @method Tech.prototype.textTracks
  7637. */
  7638. /**
  7639. * Get the remote element {@link TextTrackList}
  7640. *
  7641. * @returns {TextTrackList}
  7642. * @method Tech.prototype.remoteTextTracks
  7643. */
  7644. /**
  7645. * Get the remote element {@link HtmlTrackElementList}
  7646. *
  7647. * @returns {HtmlTrackElementList}
  7648. * @method Tech.prototype.remoteTextTrackEls
  7649. */
  7650. ALL.names.forEach(function (name) {
  7651. var props = ALL[name];
  7652. Tech.prototype[props.getterName] = function () {
  7653. this[props.privateName] = this[props.privateName] || new props.ListClass();
  7654. return this[props.privateName];
  7655. };
  7656. });
  7657. /**
  7658. * List of associated text tracks
  7659. *
  7660. * @type {TextTrackList}
  7661. * @private
  7662. * @property Tech#textTracks_
  7663. */
  7664. /**
  7665. * List of associated audio tracks.
  7666. *
  7667. * @type {AudioTrackList}
  7668. * @private
  7669. * @property Tech#audioTracks_
  7670. */
  7671. /**
  7672. * List of associated video tracks.
  7673. *
  7674. * @type {VideoTrackList}
  7675. * @private
  7676. * @property Tech#videoTracks_
  7677. */
  7678. /**
  7679. * Boolean indicating whether the `Tech` supports volume control.
  7680. *
  7681. * @type {boolean}
  7682. * @default
  7683. */
  7684. Tech.prototype.featuresVolumeControl = true;
  7685. /**
  7686. * Boolean indicating whether the `Tech` supports muting volume.
  7687. *
  7688. * @type {bolean}
  7689. * @default
  7690. */
  7691. Tech.prototype.featuresMuteControl = true;
  7692. /**
  7693. * Boolean indicating whether the `Tech` supports fullscreen resize control.
  7694. * Resizing plugins using request fullscreen reloads the plugin
  7695. *
  7696. * @type {boolean}
  7697. * @default
  7698. */
  7699. Tech.prototype.featuresFullscreenResize = false;
  7700. /**
  7701. * Boolean indicating whether the `Tech` supports changing the speed at which the video
  7702. * plays. Examples:
  7703. * - Set player to play 2x (twice) as fast
  7704. * - Set player to play 0.5x (half) as fast
  7705. *
  7706. * @type {boolean}
  7707. * @default
  7708. */
  7709. Tech.prototype.featuresPlaybackRate = false;
  7710. /**
  7711. * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
  7712. * not triggered by video-js-swf. This will be used to determine if
  7713. * {@link Tech#manualProgressOn} should be called.
  7714. *
  7715. * @type {boolean}
  7716. * @default
  7717. */
  7718. Tech.prototype.featuresProgressEvents = false;
  7719. /**
  7720. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  7721. *
  7722. * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
  7723. * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
  7724. * a new source.
  7725. *
  7726. * @type {boolean}
  7727. * @default
  7728. */
  7729. Tech.prototype.featuresSourceset = false;
  7730. /**
  7731. * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
  7732. * not triggered by video-js-swf. This will be used to determine if
  7733. * {@link Tech#manualTimeUpdates} should be called.
  7734. *
  7735. * @type {boolean}
  7736. * @default
  7737. */
  7738. Tech.prototype.featuresTimeupdateEvents = false;
  7739. /**
  7740. * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
  7741. * This will help us integrate with native `TextTrack`s if the browser supports them.
  7742. *
  7743. * @type {boolean}
  7744. * @default
  7745. */
  7746. Tech.prototype.featuresNativeTextTracks = false;
  7747. /**
  7748. * A functional mixin for techs that want to use the Source Handler pattern.
  7749. * Source handlers are scripts for handling specific formats.
  7750. * The source handler pattern is used for adaptive formats (HLS, DASH) that
  7751. * manually load video data and feed it into a Source Buffer (Media Source Extensions)
  7752. * Example: `Tech.withSourceHandlers.call(MyTech);`
  7753. *
  7754. * @param {Tech} _Tech
  7755. * The tech to add source handler functions to.
  7756. *
  7757. * @mixes Tech~SourceHandlerAdditions
  7758. */
  7759. Tech.withSourceHandlers = function (_Tech) {
  7760. /**
  7761. * Register a source handler
  7762. *
  7763. * @param {Function} handler
  7764. * The source handler class
  7765. *
  7766. * @param {number} [index]
  7767. * Register it at the following index
  7768. */
  7769. _Tech.registerSourceHandler = function (handler, index) {
  7770. var handlers = _Tech.sourceHandlers;
  7771. if (!handlers) {
  7772. handlers = _Tech.sourceHandlers = [];
  7773. }
  7774. if (index === undefined) {
  7775. // add to the end of the list
  7776. index = handlers.length;
  7777. }
  7778. handlers.splice(index, 0, handler);
  7779. };
  7780. /**
  7781. * Check if the tech can support the given type. Also checks the
  7782. * Techs sourceHandlers.
  7783. *
  7784. * @param {string} type
  7785. * The mimetype to check.
  7786. *
  7787. * @return {string}
  7788. * 'probably', 'maybe', or '' (empty string)
  7789. */
  7790. _Tech.canPlayType = function (type) {
  7791. var handlers = _Tech.sourceHandlers || [];
  7792. var can;
  7793. for (var i = 0; i < handlers.length; i++) {
  7794. can = handlers[i].canPlayType(type);
  7795. if (can) {
  7796. return can;
  7797. }
  7798. }
  7799. return '';
  7800. };
  7801. /**
  7802. * Returns the first source handler that supports the source.
  7803. *
  7804. * TODO: Answer question: should 'probably' be prioritized over 'maybe'
  7805. *
  7806. * @param {Tech~SourceObject} source
  7807. * The source object
  7808. *
  7809. * @param {Object} options
  7810. * The options passed to the tech
  7811. *
  7812. * @return {SourceHandler|null}
  7813. * The first source handler that supports the source or null if
  7814. * no SourceHandler supports the source
  7815. */
  7816. _Tech.selectSourceHandler = function (source, options) {
  7817. var handlers = _Tech.sourceHandlers || [];
  7818. var can;
  7819. for (var i = 0; i < handlers.length; i++) {
  7820. can = handlers[i].canHandleSource(source, options);
  7821. if (can) {
  7822. return handlers[i];
  7823. }
  7824. }
  7825. return null;
  7826. };
  7827. /**
  7828. * Check if the tech can support the given source.
  7829. *
  7830. * @param {Tech~SourceObject} srcObj
  7831. * The source object
  7832. *
  7833. * @param {Object} options
  7834. * The options passed to the tech
  7835. *
  7836. * @return {string}
  7837. * 'probably', 'maybe', or '' (empty string)
  7838. */
  7839. _Tech.canPlaySource = function (srcObj, options) {
  7840. var sh = _Tech.selectSourceHandler(srcObj, options);
  7841. if (sh) {
  7842. return sh.canHandleSource(srcObj, options);
  7843. }
  7844. return '';
  7845. };
  7846. /**
  7847. * When using a source handler, prefer its implementation of
  7848. * any function normally provided by the tech.
  7849. */
  7850. var deferrable = ['seekable', 'seeking', 'duration'];
  7851. /**
  7852. * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
  7853. * function if it exists, with a fallback to the Techs seekable function.
  7854. *
  7855. * @method _Tech.seekable
  7856. */
  7857. /**
  7858. * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
  7859. * function if it exists, otherwise it will fallback to the techs duration function.
  7860. *
  7861. * @method _Tech.duration
  7862. */
  7863. deferrable.forEach(function (fnName) {
  7864. var originalFn = this[fnName];
  7865. if (typeof originalFn !== 'function') {
  7866. return;
  7867. }
  7868. this[fnName] = function () {
  7869. if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
  7870. return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
  7871. }
  7872. return originalFn.apply(this, arguments);
  7873. };
  7874. }, _Tech.prototype);
  7875. /**
  7876. * Create a function for setting the source using a source object
  7877. * and source handlers.
  7878. * Should never be called unless a source handler was found.
  7879. *
  7880. * @param {Tech~SourceObject} source
  7881. * A source object with src and type keys
  7882. */
  7883. _Tech.prototype.setSource = function (source) {
  7884. var sh = _Tech.selectSourceHandler(source, this.options_);
  7885. if (!sh) {
  7886. // Fall back to a native source hander when unsupported sources are
  7887. // deliberately set
  7888. if (_Tech.nativeSourceHandler) {
  7889. sh = _Tech.nativeSourceHandler;
  7890. } else {
  7891. log.error('No source handler found for the current source.');
  7892. }
  7893. } // Dispose any existing source handler
  7894. this.disposeSourceHandler();
  7895. this.off('dispose', this.disposeSourceHandler);
  7896. if (sh !== _Tech.nativeSourceHandler) {
  7897. this.currentSource_ = source;
  7898. }
  7899. this.sourceHandler_ = sh.handleSource(source, this, this.options_);
  7900. this.one('dispose', this.disposeSourceHandler);
  7901. };
  7902. /**
  7903. * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
  7904. *
  7905. * @listens Tech#dispose
  7906. */
  7907. _Tech.prototype.disposeSourceHandler = function () {
  7908. // if we have a source and get another one
  7909. // then we are loading something new
  7910. // than clear all of our current tracks
  7911. if (this.currentSource_) {
  7912. this.clearTracks(['audio', 'video']);
  7913. this.currentSource_ = null;
  7914. } // always clean up auto-text tracks
  7915. this.cleanupAutoTextTracks();
  7916. if (this.sourceHandler_) {
  7917. if (this.sourceHandler_.dispose) {
  7918. this.sourceHandler_.dispose();
  7919. }
  7920. this.sourceHandler_ = null;
  7921. }
  7922. };
  7923. }; // The base Tech class needs to be registered as a Component. It is the only
  7924. // Tech that can be registered as a Component.
  7925. Component.registerComponent('Tech', Tech);
  7926. Tech.registerTech('Tech', Tech);
  7927. /**
  7928. * A list of techs that should be added to techOrder on Players
  7929. *
  7930. * @private
  7931. */
  7932. Tech.defaultTechOrder_ = [];
  7933. /**
  7934. * @file middleware.js
  7935. * @module middleware
  7936. */
  7937. var middlewares = {};
  7938. var middlewareInstances = {};
  7939. var TERMINATOR = {};
  7940. /**
  7941. * A middleware object is a plain JavaScript object that has methods that
  7942. * match the {@link Tech} methods found in the lists of allowed
  7943. * {@link module:middleware.allowedGetters|getters},
  7944. * {@link module:middleware.allowedSetters|setters}, and
  7945. * {@link module:middleware.allowedMediators|mediators}.
  7946. *
  7947. * @typedef {Object} MiddlewareObject
  7948. */
  7949. /**
  7950. * A middleware factory function that should return a
  7951. * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
  7952. *
  7953. * This factory will be called for each player when needed, with the player
  7954. * passed in as an argument.
  7955. *
  7956. * @callback MiddlewareFactory
  7957. * @param {Player} player
  7958. * A Video.js player.
  7959. */
  7960. /**
  7961. * Define a middleware that the player should use by way of a factory function
  7962. * that returns a middleware object.
  7963. *
  7964. * @param {string} type
  7965. * The MIME type to match or `"*"` for all MIME types.
  7966. *
  7967. * @param {MiddlewareFactory} middleware
  7968. * A middleware factory function that will be executed for
  7969. * matching types.
  7970. */
  7971. function use(type, middleware) {
  7972. middlewares[type] = middlewares[type] || [];
  7973. middlewares[type].push(middleware);
  7974. }
  7975. /**
  7976. * Asynchronously sets a source using middleware by recursing through any
  7977. * matching middlewares and calling `setSource` on each, passing along the
  7978. * previous returned value each time.
  7979. *
  7980. * @param {Player} player
  7981. * A {@link Player} instance.
  7982. *
  7983. * @param {Tech~SourceObject} src
  7984. * A source object.
  7985. *
  7986. * @param {Function}
  7987. * The next middleware to run.
  7988. */
  7989. function setSource(player, src, next) {
  7990. player.setTimeout(function () {
  7991. return setSourceHelper(src, middlewares[src.type], next, player);
  7992. }, 1);
  7993. }
  7994. /**
  7995. * When the tech is set, passes the tech to each middleware's `setTech` method.
  7996. *
  7997. * @param {Object[]} middleware
  7998. * An array of middleware instances.
  7999. *
  8000. * @param {Tech} tech
  8001. * A Video.js tech.
  8002. */
  8003. function setTech(middleware, tech) {
  8004. middleware.forEach(function (mw) {
  8005. return mw.setTech && mw.setTech(tech);
  8006. });
  8007. }
  8008. /**
  8009. * Calls a getter on the tech first, through each middleware
  8010. * from right to left to the player.
  8011. *
  8012. * @param {Object[]} middleware
  8013. * An array of middleware instances.
  8014. *
  8015. * @param {Tech} tech
  8016. * The current tech.
  8017. *
  8018. * @param {string} method
  8019. * A method name.
  8020. *
  8021. * @return {Mixed}
  8022. * The final value from the tech after middleware has intercepted it.
  8023. */
  8024. function get(middleware, tech, method) {
  8025. return middleware.reduceRight(middlewareIterator(method), tech[method]());
  8026. }
  8027. /**
  8028. * Takes the argument given to the player and calls the setter method on each
  8029. * middleware from left to right to the tech.
  8030. *
  8031. * @param {Object[]} middleware
  8032. * An array of middleware instances.
  8033. *
  8034. * @param {Tech} tech
  8035. * The current tech.
  8036. *
  8037. * @param {string} method
  8038. * A method name.
  8039. *
  8040. * @param {Mixed} arg
  8041. * The value to set on the tech.
  8042. *
  8043. * @return {Mixed}
  8044. * The return value of the `method` of the `tech`.
  8045. */
  8046. function set$1(middleware, tech, method, arg) {
  8047. return tech[method](middleware.reduce(middlewareIterator(method), arg));
  8048. }
  8049. /**
  8050. * Takes the argument given to the player and calls the `call` version of the
  8051. * method on each middleware from left to right.
  8052. *
  8053. * Then, call the passed in method on the tech and return the result unchanged
  8054. * back to the player, through middleware, this time from right to left.
  8055. *
  8056. * @param {Object[]} middleware
  8057. * An array of middleware instances.
  8058. *
  8059. * @param {Tech} tech
  8060. * The current tech.
  8061. *
  8062. * @param {string} method
  8063. * A method name.
  8064. *
  8065. * @param {Mixed} arg
  8066. * The value to set on the tech.
  8067. *
  8068. * @return {Mixed}
  8069. * The return value of the `method` of the `tech`, regardless of the
  8070. * return values of middlewares.
  8071. */
  8072. function mediate(middleware, tech, method, arg) {
  8073. if (arg === void 0) {
  8074. arg = null;
  8075. }
  8076. var callMethod = 'call' + toTitleCase(method);
  8077. var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
  8078. var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
  8079. // prevent confusion if a techs method actually returns null.
  8080. var returnValue = terminated ? null : tech[method](middlewareValue);
  8081. executeRight(middleware, method, returnValue, terminated);
  8082. return returnValue;
  8083. }
  8084. /**
  8085. * Enumeration of allowed getters where the keys are method names.
  8086. *
  8087. * @type {Object}
  8088. */
  8089. var allowedGetters = {
  8090. buffered: 1,
  8091. currentTime: 1,
  8092. duration: 1,
  8093. seekable: 1,
  8094. played: 1,
  8095. paused: 1,
  8096. volume: 1
  8097. };
  8098. /**
  8099. * Enumeration of allowed setters where the keys are method names.
  8100. *
  8101. * @type {Object}
  8102. */
  8103. var allowedSetters = {
  8104. setCurrentTime: 1,
  8105. setVolume: 1
  8106. };
  8107. /**
  8108. * Enumeration of allowed mediators where the keys are method names.
  8109. *
  8110. * @type {Object}
  8111. */
  8112. var allowedMediators = {
  8113. play: 1,
  8114. pause: 1
  8115. };
  8116. function middlewareIterator(method) {
  8117. return function (value, mw) {
  8118. // if the previous middleware terminated, pass along the termination
  8119. if (value === TERMINATOR) {
  8120. return TERMINATOR;
  8121. }
  8122. if (mw[method]) {
  8123. return mw[method](value);
  8124. }
  8125. return value;
  8126. };
  8127. }
  8128. function executeRight(mws, method, value, terminated) {
  8129. for (var i = mws.length - 1; i >= 0; i--) {
  8130. var mw = mws[i];
  8131. if (mw[method]) {
  8132. mw[method](terminated, value);
  8133. }
  8134. }
  8135. }
  8136. /**
  8137. * Clear the middleware cache for a player.
  8138. *
  8139. * @param {Player} player
  8140. * A {@link Player} instance.
  8141. */
  8142. function clearCacheForPlayer(player) {
  8143. middlewareInstances[player.id()] = null;
  8144. }
  8145. /**
  8146. * {
  8147. * [playerId]: [[mwFactory, mwInstance], ...]
  8148. * }
  8149. *
  8150. * @private
  8151. */
  8152. function getOrCreateFactory(player, mwFactory) {
  8153. var mws = middlewareInstances[player.id()];
  8154. var mw = null;
  8155. if (mws === undefined || mws === null) {
  8156. mw = mwFactory(player);
  8157. middlewareInstances[player.id()] = [[mwFactory, mw]];
  8158. return mw;
  8159. }
  8160. for (var i = 0; i < mws.length; i++) {
  8161. var _mws$i = mws[i],
  8162. mwf = _mws$i[0],
  8163. mwi = _mws$i[1];
  8164. if (mwf !== mwFactory) {
  8165. continue;
  8166. }
  8167. mw = mwi;
  8168. }
  8169. if (mw === null) {
  8170. mw = mwFactory(player);
  8171. mws.push([mwFactory, mw]);
  8172. }
  8173. return mw;
  8174. }
  8175. function setSourceHelper(src, middleware, next, player, acc, lastRun) {
  8176. if (src === void 0) {
  8177. src = {};
  8178. }
  8179. if (middleware === void 0) {
  8180. middleware = [];
  8181. }
  8182. if (acc === void 0) {
  8183. acc = [];
  8184. }
  8185. if (lastRun === void 0) {
  8186. lastRun = false;
  8187. }
  8188. var _middleware = middleware,
  8189. mwFactory = _middleware[0],
  8190. mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
  8191. if (typeof mwFactory === 'string') {
  8192. setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
  8193. // then call the mw's setSource method
  8194. } else if (mwFactory) {
  8195. var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
  8196. if (!mw.setSource) {
  8197. acc.push(mw);
  8198. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8199. }
  8200. mw.setSource(assign({}, src), function (err, _src) {
  8201. // something happened, try the next middleware on the current level
  8202. // make sure to use the old src
  8203. if (err) {
  8204. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8205. } // we've succeeded, now we need to go deeper
  8206. acc.push(mw); // if it's the same type, continue down the current chain
  8207. // otherwise, we want to go down the new chain
  8208. setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
  8209. });
  8210. } else if (mwrest.length) {
  8211. setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8212. } else if (lastRun) {
  8213. next(src, acc);
  8214. } else {
  8215. setSourceHelper(src, middlewares['*'], next, player, acc, true);
  8216. }
  8217. }
  8218. /**
  8219. * Mimetypes
  8220. *
  8221. * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
  8222. * @typedef Mimetypes~Kind
  8223. * @enum
  8224. */
  8225. var MimetypesKind = {
  8226. opus: 'video/ogg',
  8227. ogv: 'video/ogg',
  8228. mp4: 'video/mp4',
  8229. mov: 'video/mp4',
  8230. m4v: 'video/mp4',
  8231. mkv: 'video/x-matroska',
  8232. mp3: 'audio/mpeg',
  8233. aac: 'audio/aac',
  8234. oga: 'audio/ogg',
  8235. m3u8: 'application/x-mpegURL',
  8236. jpg: 'image/jpeg',
  8237. jpeg: 'image/jpeg',
  8238. gif: 'image/gif',
  8239. png: 'image/png',
  8240. svg: 'image/svg+xml',
  8241. webp: 'image/webp'
  8242. };
  8243. /**
  8244. * Get the mimetype of a given src url if possible
  8245. *
  8246. * @param {string} src
  8247. * The url to the src
  8248. *
  8249. * @return {string}
  8250. * return the mimetype if it was known or empty string otherwise
  8251. */
  8252. var getMimetype = function getMimetype(src) {
  8253. if (src === void 0) {
  8254. src = '';
  8255. }
  8256. var ext = getFileExtension(src);
  8257. var mimetype = MimetypesKind[ext.toLowerCase()];
  8258. return mimetype || '';
  8259. };
  8260. /**
  8261. * Find the mime type of a given source string if possible. Uses the player
  8262. * source cache.
  8263. *
  8264. * @param {Player} player
  8265. * The player object
  8266. *
  8267. * @param {string} src
  8268. * The source string
  8269. *
  8270. * @return {string}
  8271. * The type that was found
  8272. */
  8273. var findMimetype = function findMimetype(player, src) {
  8274. if (!src) {
  8275. return '';
  8276. } // 1. check for the type in the `source` cache
  8277. if (player.cache_.source.src === src && player.cache_.source.type) {
  8278. return player.cache_.source.type;
  8279. } // 2. see if we have this source in our `currentSources` cache
  8280. var matchingSources = player.cache_.sources.filter(function (s) {
  8281. return s.src === src;
  8282. });
  8283. if (matchingSources.length) {
  8284. return matchingSources[0].type;
  8285. } // 3. look for the src url in source elements and use the type there
  8286. var sources = player.$$('source');
  8287. for (var i = 0; i < sources.length; i++) {
  8288. var s = sources[i];
  8289. if (s.type && s.src && s.src === src) {
  8290. return s.type;
  8291. }
  8292. } // 4. finally fallback to our list of mime types based on src url extension
  8293. return getMimetype(src);
  8294. };
  8295. /**
  8296. * @module filter-source
  8297. */
  8298. /**
  8299. * Filter out single bad source objects or multiple source objects in an
  8300. * array. Also flattens nested source object arrays into a 1 dimensional
  8301. * array of source objects.
  8302. *
  8303. * @param {Tech~SourceObject|Tech~SourceObject[]} src
  8304. * The src object to filter
  8305. *
  8306. * @return {Tech~SourceObject[]}
  8307. * An array of sourceobjects containing only valid sources
  8308. *
  8309. * @private
  8310. */
  8311. var filterSource = function filterSource(src) {
  8312. // traverse array
  8313. if (Array.isArray(src)) {
  8314. var newsrc = [];
  8315. src.forEach(function (srcobj) {
  8316. srcobj = filterSource(srcobj);
  8317. if (Array.isArray(srcobj)) {
  8318. newsrc = newsrc.concat(srcobj);
  8319. } else if (isObject(srcobj)) {
  8320. newsrc.push(srcobj);
  8321. }
  8322. });
  8323. src = newsrc;
  8324. } else if (typeof src === 'string' && src.trim()) {
  8325. // convert string into object
  8326. src = [fixSource({
  8327. src: src
  8328. })];
  8329. } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
  8330. // src is already valid
  8331. src = [fixSource(src)];
  8332. } else {
  8333. // invalid source, turn it into an empty array
  8334. src = [];
  8335. }
  8336. return src;
  8337. };
  8338. /**
  8339. * Checks src mimetype, adding it when possible
  8340. *
  8341. * @param {Tech~SourceObject} src
  8342. * The src object to check
  8343. * @return {Tech~SourceObject}
  8344. * src Object with known type
  8345. */
  8346. function fixSource(src) {
  8347. var mimetype = getMimetype(src.src);
  8348. if (!src.type && mimetype) {
  8349. src.type = mimetype;
  8350. }
  8351. return src;
  8352. }
  8353. /**
  8354. * The `MediaLoader` is the `Component` that decides which playback technology to load
  8355. * when a player is initialized.
  8356. *
  8357. * @extends Component
  8358. */
  8359. var MediaLoader =
  8360. /*#__PURE__*/
  8361. function (_Component) {
  8362. _inheritsLoose(MediaLoader, _Component);
  8363. /**
  8364. * Create an instance of this class.
  8365. *
  8366. * @param {Player} player
  8367. * The `Player` that this class should attach to.
  8368. *
  8369. * @param {Object} [options]
  8370. * The key/value store of player options.
  8371. *
  8372. * @param {Component~ReadyCallback} [ready]
  8373. * The function that is run when this component is ready.
  8374. */
  8375. function MediaLoader(player, options, ready) {
  8376. var _this;
  8377. // MediaLoader has no element
  8378. var options_ = mergeOptions({
  8379. createEl: false
  8380. }, options);
  8381. _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
  8382. // load the first supported playback technology.
  8383. if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
  8384. for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
  8385. var techName = toTitleCase(j[i]);
  8386. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  8387. // Remove once that deprecated behavior is removed.
  8388. if (!techName) {
  8389. tech = Component.getComponent(techName);
  8390. } // Check if the browser supports this technology
  8391. if (tech && tech.isSupported()) {
  8392. player.loadTech_(techName);
  8393. break;
  8394. }
  8395. }
  8396. } else {
  8397. // Loop through playback technologies (HTML5, Flash) and check for support.
  8398. // Then load the best source.
  8399. // A few assumptions here:
  8400. // All playback technologies respect preload false.
  8401. player.src(options.playerOptions.sources);
  8402. }
  8403. return _this;
  8404. }
  8405. return MediaLoader;
  8406. }(Component);
  8407. Component.registerComponent('MediaLoader', MediaLoader);
  8408. /**
  8409. * Clickable Component which is clickable or keyboard actionable,
  8410. * but is not a native HTML button.
  8411. *
  8412. * @extends Component
  8413. */
  8414. var ClickableComponent =
  8415. /*#__PURE__*/
  8416. function (_Component) {
  8417. _inheritsLoose(ClickableComponent, _Component);
  8418. /**
  8419. * Creates an instance of this class.
  8420. *
  8421. * @param {Player} player
  8422. * The `Player` that this class should be attached to.
  8423. *
  8424. * @param {Object} [options]
  8425. * The key/value store of player options.
  8426. */
  8427. function ClickableComponent(player, options) {
  8428. var _this;
  8429. _this = _Component.call(this, player, options) || this;
  8430. _this.emitTapEvents();
  8431. _this.enable();
  8432. return _this;
  8433. }
  8434. /**
  8435. * Create the `Component`s DOM element.
  8436. *
  8437. * @param {string} [tag=div]
  8438. * The element's node type.
  8439. *
  8440. * @param {Object} [props={}]
  8441. * An object of properties that should be set on the element.
  8442. *
  8443. * @param {Object} [attributes={}]
  8444. * An object of attributes that should be set on the element.
  8445. *
  8446. * @return {Element}
  8447. * The element that gets created.
  8448. */
  8449. var _proto = ClickableComponent.prototype;
  8450. _proto.createEl = function createEl$$1(tag, props, attributes) {
  8451. if (tag === void 0) {
  8452. tag = 'div';
  8453. }
  8454. if (props === void 0) {
  8455. props = {};
  8456. }
  8457. if (attributes === void 0) {
  8458. attributes = {};
  8459. }
  8460. props = assign({
  8461. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  8462. className: this.buildCSSClass(),
  8463. tabIndex: 0
  8464. }, props);
  8465. if (tag === 'button') {
  8466. log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
  8467. } // Add ARIA attributes for clickable element which is not a native HTML button
  8468. attributes = assign({
  8469. role: 'button'
  8470. }, attributes);
  8471. this.tabIndex_ = props.tabIndex;
  8472. var el = _Component.prototype.createEl.call(this, tag, props, attributes);
  8473. this.createControlTextEl(el);
  8474. return el;
  8475. };
  8476. _proto.dispose = function dispose() {
  8477. // remove controlTextEl_ on dispose
  8478. this.controlTextEl_ = null;
  8479. _Component.prototype.dispose.call(this);
  8480. }
  8481. /**
  8482. * Create a control text element on this `Component`
  8483. *
  8484. * @param {Element} [el]
  8485. * Parent element for the control text.
  8486. *
  8487. * @return {Element}
  8488. * The control text element that gets created.
  8489. */
  8490. ;
  8491. _proto.createControlTextEl = function createControlTextEl(el) {
  8492. this.controlTextEl_ = createEl('span', {
  8493. className: 'vjs-control-text'
  8494. }, {
  8495. // let the screen reader user know that the text of the element may change
  8496. 'aria-live': 'polite'
  8497. });
  8498. if (el) {
  8499. el.appendChild(this.controlTextEl_);
  8500. }
  8501. this.controlText(this.controlText_, el);
  8502. return this.controlTextEl_;
  8503. }
  8504. /**
  8505. * Get or set the localize text to use for the controls on the `Component`.
  8506. *
  8507. * @param {string} [text]
  8508. * Control text for element.
  8509. *
  8510. * @param {Element} [el=this.el()]
  8511. * Element to set the title on.
  8512. *
  8513. * @return {string}
  8514. * - The control text when getting
  8515. */
  8516. ;
  8517. _proto.controlText = function controlText(text, el) {
  8518. if (el === void 0) {
  8519. el = this.el();
  8520. }
  8521. if (text === undefined) {
  8522. return this.controlText_ || 'Need Text';
  8523. }
  8524. var localizedText = this.localize(text);
  8525. this.controlText_ = text;
  8526. textContent(this.controlTextEl_, localizedText);
  8527. if (!this.nonIconControl) {
  8528. // Set title attribute if only an icon is shown
  8529. el.setAttribute('title', localizedText);
  8530. }
  8531. }
  8532. /**
  8533. * Builds the default DOM `className`.
  8534. *
  8535. * @return {string}
  8536. * The DOM `className` for this object.
  8537. */
  8538. ;
  8539. _proto.buildCSSClass = function buildCSSClass() {
  8540. return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
  8541. }
  8542. /**
  8543. * Enable this `Component`s element.
  8544. */
  8545. ;
  8546. _proto.enable = function enable() {
  8547. if (!this.enabled_) {
  8548. this.enabled_ = true;
  8549. this.removeClass('vjs-disabled');
  8550. this.el_.setAttribute('aria-disabled', 'false');
  8551. if (typeof this.tabIndex_ !== 'undefined') {
  8552. this.el_.setAttribute('tabIndex', this.tabIndex_);
  8553. }
  8554. this.on(['tap', 'click'], this.handleClick);
  8555. this.on('focus', this.handleFocus);
  8556. this.on('blur', this.handleBlur);
  8557. }
  8558. }
  8559. /**
  8560. * Disable this `Component`s element.
  8561. */
  8562. ;
  8563. _proto.disable = function disable() {
  8564. this.enabled_ = false;
  8565. this.addClass('vjs-disabled');
  8566. this.el_.setAttribute('aria-disabled', 'true');
  8567. if (typeof this.tabIndex_ !== 'undefined') {
  8568. this.el_.removeAttribute('tabIndex');
  8569. }
  8570. this.off(['tap', 'click'], this.handleClick);
  8571. this.off('focus', this.handleFocus);
  8572. this.off('blur', this.handleBlur);
  8573. }
  8574. /**
  8575. * This gets called when a `ClickableComponent` gets:
  8576. * - Clicked (via the `click` event, listening starts in the constructor)
  8577. * - Tapped (via the `tap` event, listening starts in the constructor)
  8578. * - The following things happen in order:
  8579. * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
  8580. * `ClickableComponent`.
  8581. * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
  8582. * {@link ClickableComponent#handleKeyPress}.
  8583. * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
  8584. * the space or enter key.
  8585. * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
  8586. * event as a parameter.
  8587. *
  8588. * @param {EventTarget~Event} event
  8589. * The `keydown`, `tap`, or `click` event that caused this function to be
  8590. * called.
  8591. *
  8592. * @listens tap
  8593. * @listens click
  8594. * @abstract
  8595. */
  8596. ;
  8597. _proto.handleClick = function handleClick(event) {}
  8598. /**
  8599. * This gets called when a `ClickableComponent` gains focus via a `focus` event.
  8600. * Turns on listening for `keydown` events. When they happen it
  8601. * calls `this.handleKeyPress`.
  8602. *
  8603. * @param {EventTarget~Event} event
  8604. * The `focus` event that caused this function to be called.
  8605. *
  8606. * @listens focus
  8607. */
  8608. ;
  8609. _proto.handleFocus = function handleFocus(event) {
  8610. on(document, 'keydown', bind(this, this.handleKeyPress));
  8611. }
  8612. /**
  8613. * Called when this ClickableComponent has focus and a key gets pressed down. By
  8614. * default it will call `this.handleClick` when the key is space or enter.
  8615. *
  8616. * @param {EventTarget~Event} event
  8617. * The `keydown` event that caused this function to be called.
  8618. *
  8619. * @listens keydown
  8620. */
  8621. ;
  8622. _proto.handleKeyPress = function handleKeyPress(event) {
  8623. // Support Space or Enter key operation to fire a click event
  8624. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  8625. event.preventDefault();
  8626. this.trigger('click');
  8627. } else {
  8628. // Pass keypress handling up for unsupported keys
  8629. _Component.prototype.handleKeyPress.call(this, event);
  8630. }
  8631. }
  8632. /**
  8633. * Called when a `ClickableComponent` loses focus. Turns off the listener for
  8634. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  8635. *
  8636. * @param {EventTarget~Event} event
  8637. * The `blur` event that caused this function to be called.
  8638. *
  8639. * @listens blur
  8640. */
  8641. ;
  8642. _proto.handleBlur = function handleBlur(event) {
  8643. off(document, 'keydown', bind(this, this.handleKeyPress));
  8644. };
  8645. return ClickableComponent;
  8646. }(Component);
  8647. Component.registerComponent('ClickableComponent', ClickableComponent);
  8648. /**
  8649. * A `ClickableComponent` that handles showing the poster image for the player.
  8650. *
  8651. * @extends ClickableComponent
  8652. */
  8653. var PosterImage =
  8654. /*#__PURE__*/
  8655. function (_ClickableComponent) {
  8656. _inheritsLoose(PosterImage, _ClickableComponent);
  8657. /**
  8658. * Create an instance of this class.
  8659. *
  8660. * @param {Player} player
  8661. * The `Player` that this class should attach to.
  8662. *
  8663. * @param {Object} [options]
  8664. * The key/value store of player options.
  8665. */
  8666. function PosterImage(player, options) {
  8667. var _this;
  8668. _this = _ClickableComponent.call(this, player, options) || this;
  8669. _this.update();
  8670. player.on('posterchange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
  8671. return _this;
  8672. }
  8673. /**
  8674. * Clean up and dispose of the `PosterImage`.
  8675. */
  8676. var _proto = PosterImage.prototype;
  8677. _proto.dispose = function dispose() {
  8678. this.player().off('posterchange', this.update);
  8679. _ClickableComponent.prototype.dispose.call(this);
  8680. }
  8681. /**
  8682. * Create the `PosterImage`s DOM element.
  8683. *
  8684. * @return {Element}
  8685. * The element that gets created.
  8686. */
  8687. ;
  8688. _proto.createEl = function createEl$$1() {
  8689. var el = createEl('div', {
  8690. className: 'vjs-poster',
  8691. // Don't want poster to be tabbable.
  8692. tabIndex: -1
  8693. });
  8694. return el;
  8695. }
  8696. /**
  8697. * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
  8698. *
  8699. * @listens Player#posterchange
  8700. *
  8701. * @param {EventTarget~Event} [event]
  8702. * The `Player#posterchange` event that triggered this function.
  8703. */
  8704. ;
  8705. _proto.update = function update(event) {
  8706. var url = this.player().poster();
  8707. this.setSrc(url); // If there's no poster source we should display:none on this component
  8708. // so it's not still clickable or right-clickable
  8709. if (url) {
  8710. this.show();
  8711. } else {
  8712. this.hide();
  8713. }
  8714. }
  8715. /**
  8716. * Set the source of the `PosterImage` depending on the display method.
  8717. *
  8718. * @param {string} url
  8719. * The URL to the source for the `PosterImage`.
  8720. */
  8721. ;
  8722. _proto.setSrc = function setSrc(url) {
  8723. var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
  8724. // this will throw an extra error
  8725. if (url) {
  8726. backgroundImage = "url(\"" + url + "\")";
  8727. }
  8728. this.el_.style.backgroundImage = backgroundImage;
  8729. }
  8730. /**
  8731. * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
  8732. * {@link ClickableComponent#handleClick} for instances where this will be triggered.
  8733. *
  8734. * @listens tap
  8735. * @listens click
  8736. * @listens keydown
  8737. *
  8738. * @param {EventTarget~Event} event
  8739. + The `click`, `tap` or `keydown` event that caused this function to be called.
  8740. */
  8741. ;
  8742. _proto.handleClick = function handleClick(event) {
  8743. // We don't want a click to trigger playback when controls are disabled
  8744. if (!this.player_.controls()) {
  8745. return;
  8746. }
  8747. if (this.player_.paused()) {
  8748. silencePromise(this.player_.play());
  8749. } else {
  8750. this.player_.pause();
  8751. } // call handleFocus manually to get hotkeys working
  8752. this.player_.handleFocus({});
  8753. };
  8754. return PosterImage;
  8755. }(ClickableComponent);
  8756. Component.registerComponent('PosterImage', PosterImage);
  8757. var darkGray = '#222';
  8758. var lightGray = '#ccc';
  8759. var fontMap = {
  8760. monospace: 'monospace',
  8761. sansSerif: 'sans-serif',
  8762. serif: 'serif',
  8763. monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
  8764. monospaceSerif: '"Courier New", monospace',
  8765. proportionalSansSerif: 'sans-serif',
  8766. proportionalSerif: 'serif',
  8767. casual: '"Comic Sans MS", Impact, fantasy',
  8768. script: '"Monotype Corsiva", cursive',
  8769. smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
  8770. };
  8771. /**
  8772. * Construct an rgba color from a given hex color code.
  8773. *
  8774. * @param {number} color
  8775. * Hex number for color, like #f0e or #f604e2.
  8776. *
  8777. * @param {number} opacity
  8778. * Value for opacity, 0.0 - 1.0.
  8779. *
  8780. * @return {string}
  8781. * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
  8782. */
  8783. function constructColor(color, opacity) {
  8784. var hex;
  8785. if (color.length === 4) {
  8786. // color looks like "#f0e"
  8787. hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
  8788. } else if (color.length === 7) {
  8789. // color looks like "#f604e2"
  8790. hex = color.slice(1);
  8791. } else {
  8792. throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
  8793. }
  8794. return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
  8795. }
  8796. /**
  8797. * Try to update the style of a DOM element. Some style changes will throw an error,
  8798. * particularly in IE8. Those should be noops.
  8799. *
  8800. * @param {Element} el
  8801. * The DOM element to be styled.
  8802. *
  8803. * @param {string} style
  8804. * The CSS property on the element that should be styled.
  8805. *
  8806. * @param {string} rule
  8807. * The style rule that should be applied to the property.
  8808. *
  8809. * @private
  8810. */
  8811. function tryUpdateStyle(el, style, rule) {
  8812. try {
  8813. el.style[style] = rule;
  8814. } catch (e) {
  8815. // Satisfies linter.
  8816. return;
  8817. }
  8818. }
  8819. /**
  8820. * The component for displaying text track cues.
  8821. *
  8822. * @extends Component
  8823. */
  8824. var TextTrackDisplay =
  8825. /*#__PURE__*/
  8826. function (_Component) {
  8827. _inheritsLoose(TextTrackDisplay, _Component);
  8828. /**
  8829. * Creates an instance of this class.
  8830. *
  8831. * @param {Player} player
  8832. * The `Player` that this class should be attached to.
  8833. *
  8834. * @param {Object} [options]
  8835. * The key/value store of player options.
  8836. *
  8837. * @param {Component~ReadyCallback} [ready]
  8838. * The function to call when `TextTrackDisplay` is ready.
  8839. */
  8840. function TextTrackDisplay(player, options, ready) {
  8841. var _this;
  8842. _this = _Component.call(this, player, options, ready) || this;
  8843. var updateDisplayHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay);
  8844. player.on('loadstart', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.toggleDisplay));
  8845. player.on('texttrackchange', updateDisplayHandler);
  8846. player.on('loadedmetadata', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.preselectTrack)); // This used to be called during player init, but was causing an error
  8847. // if a track should show by default and the display hadn't loaded yet.
  8848. // Should probably be moved to an external track loader when we support
  8849. // tracks that don't need a display.
  8850. player.ready(bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
  8851. if (player.tech_ && player.tech_.featuresNativeTextTracks) {
  8852. this.hide();
  8853. return;
  8854. }
  8855. player.on('fullscreenchange', updateDisplayHandler);
  8856. player.on('playerresize', updateDisplayHandler);
  8857. window$1.addEventListener('orientationchange', updateDisplayHandler);
  8858. player.on('dispose', function () {
  8859. return window$1.removeEventListener('orientationchange', updateDisplayHandler);
  8860. });
  8861. var tracks = this.options_.playerOptions.tracks || [];
  8862. for (var i = 0; i < tracks.length; i++) {
  8863. this.player_.addRemoteTextTrack(tracks[i], true);
  8864. }
  8865. this.preselectTrack();
  8866. }));
  8867. return _this;
  8868. }
  8869. /**
  8870. * Preselect a track following this precedence:
  8871. * - matches the previously selected {@link TextTrack}'s language and kind
  8872. * - matches the previously selected {@link TextTrack}'s language only
  8873. * - is the first default captions track
  8874. * - is the first default descriptions track
  8875. *
  8876. * @listens Player#loadstart
  8877. */
  8878. var _proto = TextTrackDisplay.prototype;
  8879. _proto.preselectTrack = function preselectTrack() {
  8880. var modes = {
  8881. captions: 1,
  8882. subtitles: 1
  8883. };
  8884. var trackList = this.player_.textTracks();
  8885. var userPref = this.player_.cache_.selectedLanguage;
  8886. var firstDesc;
  8887. var firstCaptions;
  8888. var preferredTrack;
  8889. for (var i = 0; i < trackList.length; i++) {
  8890. var track = trackList[i];
  8891. if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
  8892. // Always choose the track that matches both language and kind
  8893. if (track.kind === userPref.kind) {
  8894. preferredTrack = track; // or choose the first track that matches language
  8895. } else if (!preferredTrack) {
  8896. preferredTrack = track;
  8897. } // clear everything if offTextTrackMenuItem was clicked
  8898. } else if (userPref && !userPref.enabled) {
  8899. preferredTrack = null;
  8900. firstDesc = null;
  8901. firstCaptions = null;
  8902. } else if (track.default) {
  8903. if (track.kind === 'descriptions' && !firstDesc) {
  8904. firstDesc = track;
  8905. } else if (track.kind in modes && !firstCaptions) {
  8906. firstCaptions = track;
  8907. }
  8908. }
  8909. } // The preferredTrack matches the user preference and takes
  8910. // precedence over all the other tracks.
  8911. // So, display the preferredTrack before the first default track
  8912. // and the subtitles/captions track before the descriptions track
  8913. if (preferredTrack) {
  8914. preferredTrack.mode = 'showing';
  8915. } else if (firstCaptions) {
  8916. firstCaptions.mode = 'showing';
  8917. } else if (firstDesc) {
  8918. firstDesc.mode = 'showing';
  8919. }
  8920. }
  8921. /**
  8922. * Turn display of {@link TextTrack}'s from the current state into the other state.
  8923. * There are only two states:
  8924. * - 'shown'
  8925. * - 'hidden'
  8926. *
  8927. * @listens Player#loadstart
  8928. */
  8929. ;
  8930. _proto.toggleDisplay = function toggleDisplay() {
  8931. if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
  8932. this.hide();
  8933. } else {
  8934. this.show();
  8935. }
  8936. }
  8937. /**
  8938. * Create the {@link Component}'s DOM element.
  8939. *
  8940. * @return {Element}
  8941. * The element that was created.
  8942. */
  8943. ;
  8944. _proto.createEl = function createEl() {
  8945. return _Component.prototype.createEl.call(this, 'div', {
  8946. className: 'vjs-text-track-display'
  8947. }, {
  8948. 'aria-live': 'off',
  8949. 'aria-atomic': 'true'
  8950. });
  8951. }
  8952. /**
  8953. * Clear all displayed {@link TextTrack}s.
  8954. */
  8955. ;
  8956. _proto.clearDisplay = function clearDisplay() {
  8957. if (typeof window$1.WebVTT === 'function') {
  8958. window$1.WebVTT.processCues(window$1, [], this.el_);
  8959. }
  8960. }
  8961. /**
  8962. * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
  8963. * a {@link Player#fullscreenchange} is fired.
  8964. *
  8965. * @listens Player#texttrackchange
  8966. * @listens Player#fullscreenchange
  8967. */
  8968. ;
  8969. _proto.updateDisplay = function updateDisplay() {
  8970. var tracks = this.player_.textTracks();
  8971. this.clearDisplay(); // Track display prioritization model: if multiple tracks are 'showing',
  8972. // display the first 'subtitles' or 'captions' track which is 'showing',
  8973. // otherwise display the first 'descriptions' track which is 'showing'
  8974. var descriptionsTrack = null;
  8975. var captionsSubtitlesTrack = null;
  8976. var i = tracks.length;
  8977. while (i--) {
  8978. var track = tracks[i];
  8979. if (track.mode === 'showing') {
  8980. if (track.kind === 'descriptions') {
  8981. descriptionsTrack = track;
  8982. } else {
  8983. captionsSubtitlesTrack = track;
  8984. }
  8985. }
  8986. }
  8987. if (captionsSubtitlesTrack) {
  8988. if (this.getAttribute('aria-live') !== 'off') {
  8989. this.setAttribute('aria-live', 'off');
  8990. }
  8991. this.updateForTrack(captionsSubtitlesTrack);
  8992. } else if (descriptionsTrack) {
  8993. if (this.getAttribute('aria-live') !== 'assertive') {
  8994. this.setAttribute('aria-live', 'assertive');
  8995. }
  8996. this.updateForTrack(descriptionsTrack);
  8997. }
  8998. }
  8999. /**
  9000. * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
  9001. *
  9002. * @param {TextTrack} track
  9003. * Text track object to be added to the list.
  9004. */
  9005. ;
  9006. _proto.updateForTrack = function updateForTrack(track) {
  9007. if (typeof window$1.WebVTT !== 'function' || !track.activeCues) {
  9008. return;
  9009. }
  9010. var cues = [];
  9011. for (var _i = 0; _i < track.activeCues.length; _i++) {
  9012. cues.push(track.activeCues[_i]);
  9013. }
  9014. window$1.WebVTT.processCues(window$1, cues, this.el_);
  9015. if (!this.player_.textTrackSettings) {
  9016. return;
  9017. }
  9018. var overrides = this.player_.textTrackSettings.getValues();
  9019. var i = cues.length;
  9020. while (i--) {
  9021. var cue = cues[i];
  9022. if (!cue) {
  9023. continue;
  9024. }
  9025. var cueDiv = cue.displayState;
  9026. if (overrides.color) {
  9027. cueDiv.firstChild.style.color = overrides.color;
  9028. }
  9029. if (overrides.textOpacity) {
  9030. tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
  9031. }
  9032. if (overrides.backgroundColor) {
  9033. cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
  9034. }
  9035. if (overrides.backgroundOpacity) {
  9036. tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
  9037. }
  9038. if (overrides.windowColor) {
  9039. if (overrides.windowOpacity) {
  9040. tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
  9041. } else {
  9042. cueDiv.style.backgroundColor = overrides.windowColor;
  9043. }
  9044. }
  9045. if (overrides.edgeStyle) {
  9046. if (overrides.edgeStyle === 'dropshadow') {
  9047. cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
  9048. } else if (overrides.edgeStyle === 'raised') {
  9049. cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
  9050. } else if (overrides.edgeStyle === 'depressed') {
  9051. cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
  9052. } else if (overrides.edgeStyle === 'uniform') {
  9053. cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
  9054. }
  9055. }
  9056. if (overrides.fontPercent && overrides.fontPercent !== 1) {
  9057. var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
  9058. cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
  9059. cueDiv.style.height = 'auto';
  9060. cueDiv.style.top = 'auto';
  9061. cueDiv.style.bottom = '2px';
  9062. }
  9063. if (overrides.fontFamily && overrides.fontFamily !== 'default') {
  9064. if (overrides.fontFamily === 'small-caps') {
  9065. cueDiv.firstChild.style.fontVariant = 'small-caps';
  9066. } else {
  9067. cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
  9068. }
  9069. }
  9070. }
  9071. };
  9072. return TextTrackDisplay;
  9073. }(Component);
  9074. Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
  9075. /**
  9076. * A loading spinner for use during waiting/loading events.
  9077. *
  9078. * @extends Component
  9079. */
  9080. var LoadingSpinner =
  9081. /*#__PURE__*/
  9082. function (_Component) {
  9083. _inheritsLoose(LoadingSpinner, _Component);
  9084. function LoadingSpinner() {
  9085. return _Component.apply(this, arguments) || this;
  9086. }
  9087. var _proto = LoadingSpinner.prototype;
  9088. /**
  9089. * Create the `LoadingSpinner`s DOM element.
  9090. *
  9091. * @return {Element}
  9092. * The dom element that gets created.
  9093. */
  9094. _proto.createEl = function createEl$$1() {
  9095. var isAudio = this.player_.isAudio();
  9096. var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
  9097. var controlText = createEl('span', {
  9098. className: 'vjs-control-text',
  9099. innerHTML: this.localize('{1} is loading.', [playerType])
  9100. });
  9101. var el = _Component.prototype.createEl.call(this, 'div', {
  9102. className: 'vjs-loading-spinner',
  9103. dir: 'ltr'
  9104. });
  9105. el.appendChild(controlText);
  9106. return el;
  9107. };
  9108. return LoadingSpinner;
  9109. }(Component);
  9110. Component.registerComponent('LoadingSpinner', LoadingSpinner);
  9111. /**
  9112. * Base class for all buttons.
  9113. *
  9114. * @extends ClickableComponent
  9115. */
  9116. var Button =
  9117. /*#__PURE__*/
  9118. function (_ClickableComponent) {
  9119. _inheritsLoose(Button, _ClickableComponent);
  9120. function Button() {
  9121. return _ClickableComponent.apply(this, arguments) || this;
  9122. }
  9123. var _proto = Button.prototype;
  9124. /**
  9125. * Create the `Button`s DOM element.
  9126. *
  9127. * @param {string} [tag="button"]
  9128. * The element's node type. This argument is IGNORED: no matter what
  9129. * is passed, it will always create a `button` element.
  9130. *
  9131. * @param {Object} [props={}]
  9132. * An object of properties that should be set on the element.
  9133. *
  9134. * @param {Object} [attributes={}]
  9135. * An object of attributes that should be set on the element.
  9136. *
  9137. * @return {Element}
  9138. * The element that gets created.
  9139. */
  9140. _proto.createEl = function createEl(tag, props, attributes) {
  9141. if (props === void 0) {
  9142. props = {};
  9143. }
  9144. if (attributes === void 0) {
  9145. attributes = {};
  9146. }
  9147. tag = 'button';
  9148. props = assign({
  9149. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  9150. className: this.buildCSSClass()
  9151. }, props); // Add attributes for button element
  9152. attributes = assign({
  9153. // Necessary since the default button type is "submit"
  9154. type: 'button'
  9155. }, attributes);
  9156. var el = Component.prototype.createEl.call(this, tag, props, attributes);
  9157. this.createControlTextEl(el);
  9158. return el;
  9159. }
  9160. /**
  9161. * Add a child `Component` inside of this `Button`.
  9162. *
  9163. * @param {string|Component} child
  9164. * The name or instance of a child to add.
  9165. *
  9166. * @param {Object} [options={}]
  9167. * The key/value store of options that will get passed to children of
  9168. * the child.
  9169. *
  9170. * @return {Component}
  9171. * The `Component` that gets added as a child. When using a string the
  9172. * `Component` will get created by this process.
  9173. *
  9174. * @deprecated since version 5
  9175. */
  9176. ;
  9177. _proto.addChild = function addChild(child, options) {
  9178. if (options === void 0) {
  9179. options = {};
  9180. }
  9181. var className = this.constructor.name;
  9182. 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
  9183. return Component.prototype.addChild.call(this, child, options);
  9184. }
  9185. /**
  9186. * Enable the `Button` element so that it can be activated or clicked. Use this with
  9187. * {@link Button#disable}.
  9188. */
  9189. ;
  9190. _proto.enable = function enable() {
  9191. _ClickableComponent.prototype.enable.call(this);
  9192. this.el_.removeAttribute('disabled');
  9193. }
  9194. /**
  9195. * Disable the `Button` element so that it cannot be activated or clicked. Use this with
  9196. * {@link Button#enable}.
  9197. */
  9198. ;
  9199. _proto.disable = function disable() {
  9200. _ClickableComponent.prototype.disable.call(this);
  9201. this.el_.setAttribute('disabled', 'disabled');
  9202. }
  9203. /**
  9204. * This gets called when a `Button` has focus and `keydown` is triggered via a key
  9205. * press.
  9206. *
  9207. * @param {EventTarget~Event} event
  9208. * The event that caused this function to get called.
  9209. *
  9210. * @listens keydown
  9211. */
  9212. ;
  9213. _proto.handleKeyPress = function handleKeyPress(event) {
  9214. // Ignore Space or Enter key operation, which is handled by the browser for a button.
  9215. if (!(keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter'))) {
  9216. // Pass keypress handling up for unsupported keys
  9217. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  9218. }
  9219. };
  9220. return Button;
  9221. }(ClickableComponent);
  9222. Component.registerComponent('Button', Button);
  9223. /**
  9224. * The initial play button that shows before the video has played. The hiding of the
  9225. * `BigPlayButton` get done via CSS and `Player` states.
  9226. *
  9227. * @extends Button
  9228. */
  9229. var BigPlayButton =
  9230. /*#__PURE__*/
  9231. function (_Button) {
  9232. _inheritsLoose(BigPlayButton, _Button);
  9233. function BigPlayButton(player, options) {
  9234. var _this;
  9235. _this = _Button.call(this, player, options) || this;
  9236. _this.mouseused_ = false;
  9237. _this.on('mousedown', _this.handleMouseDown);
  9238. return _this;
  9239. }
  9240. /**
  9241. * Builds the default DOM `className`.
  9242. *
  9243. * @return {string}
  9244. * The DOM `className` for this object. Always returns 'vjs-big-play-button'.
  9245. */
  9246. var _proto = BigPlayButton.prototype;
  9247. _proto.buildCSSClass = function buildCSSClass() {
  9248. return 'vjs-big-play-button';
  9249. }
  9250. /**
  9251. * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
  9252. * for more detailed information on what a click can be.
  9253. *
  9254. * @param {EventTarget~Event} event
  9255. * The `keydown`, `tap`, or `click` event that caused this function to be
  9256. * called.
  9257. *
  9258. * @listens tap
  9259. * @listens click
  9260. */
  9261. ;
  9262. _proto.handleClick = function handleClick(event) {
  9263. var playPromise = this.player_.play(); // exit early if clicked via the mouse
  9264. if (this.mouseused_ && event.clientX && event.clientY) {
  9265. silencePromise(playPromise); // call handleFocus manually to get hotkeys working
  9266. this.player_.handleFocus({});
  9267. return;
  9268. }
  9269. var cb = this.player_.getChild('controlBar');
  9270. var playToggle = cb && cb.getChild('playToggle');
  9271. if (!playToggle) {
  9272. this.player_.focus();
  9273. return;
  9274. }
  9275. var playFocus = function playFocus() {
  9276. return playToggle.focus();
  9277. };
  9278. if (isPromise(playPromise)) {
  9279. playPromise.then(playFocus, function () {});
  9280. } else {
  9281. this.setTimeout(playFocus, 1);
  9282. }
  9283. };
  9284. _proto.handleKeyPress = function handleKeyPress(event) {
  9285. this.mouseused_ = false;
  9286. _Button.prototype.handleKeyPress.call(this, event);
  9287. };
  9288. _proto.handleMouseDown = function handleMouseDown(event) {
  9289. this.mouseused_ = true;
  9290. };
  9291. return BigPlayButton;
  9292. }(Button);
  9293. /**
  9294. * The text that should display over the `BigPlayButton`s controls. Added to for localization.
  9295. *
  9296. * @type {string}
  9297. * @private
  9298. */
  9299. BigPlayButton.prototype.controlText_ = 'Play Video';
  9300. Component.registerComponent('BigPlayButton', BigPlayButton);
  9301. /**
  9302. * The `CloseButton` is a `{@link Button}` that fires a `close` event when
  9303. * it gets clicked.
  9304. *
  9305. * @extends Button
  9306. */
  9307. var CloseButton =
  9308. /*#__PURE__*/
  9309. function (_Button) {
  9310. _inheritsLoose(CloseButton, _Button);
  9311. /**
  9312. * Creates an instance of the this class.
  9313. *
  9314. * @param {Player} player
  9315. * The `Player` that this class should be attached to.
  9316. *
  9317. * @param {Object} [options]
  9318. * The key/value store of player options.
  9319. */
  9320. function CloseButton(player, options) {
  9321. var _this;
  9322. _this = _Button.call(this, player, options) || this;
  9323. _this.controlText(options && options.controlText || _this.localize('Close'));
  9324. return _this;
  9325. }
  9326. /**
  9327. * Builds the default DOM `className`.
  9328. *
  9329. * @return {string}
  9330. * The DOM `className` for this object.
  9331. */
  9332. var _proto = CloseButton.prototype;
  9333. _proto.buildCSSClass = function buildCSSClass() {
  9334. return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
  9335. }
  9336. /**
  9337. * This gets called when a `CloseButton` has focus and `keydown` is triggered via a key
  9338. * press.
  9339. *
  9340. * @param {EventTarget~Event} event
  9341. * The event that caused this function to get called.
  9342. *
  9343. * @listens keydown
  9344. */
  9345. ;
  9346. _proto.handleKeyPress = function handleKeyPress(event) {} // Override the default `Button` behavior, and don't pass the keypress event
  9347. // up to the player because this button is part of a `ModalDialog`, which
  9348. // doesn't pass keypresses to the player either.
  9349. /**
  9350. * This gets called when a `CloseButton` gets clicked. See
  9351. * {@link ClickableComponent#handleClick} for more information on when this will be
  9352. * triggered
  9353. *
  9354. * @param {EventTarget~Event} event
  9355. * The `keydown`, `tap`, or `click` event that caused this function to be
  9356. * called.
  9357. *
  9358. * @listens tap
  9359. * @listens click
  9360. * @fires CloseButton#close
  9361. */
  9362. ;
  9363. _proto.handleClick = function handleClick(event) {
  9364. /**
  9365. * Triggered when the a `CloseButton` is clicked.
  9366. *
  9367. * @event CloseButton#close
  9368. * @type {EventTarget~Event}
  9369. *
  9370. * @property {boolean} [bubbles=false]
  9371. * set to false so that the close event does not
  9372. * bubble up to parents if there is no listener
  9373. */
  9374. this.trigger({
  9375. type: 'close',
  9376. bubbles: false
  9377. });
  9378. };
  9379. return CloseButton;
  9380. }(Button);
  9381. Component.registerComponent('CloseButton', CloseButton);
  9382. /**
  9383. * Button to toggle between play and pause.
  9384. *
  9385. * @extends Button
  9386. */
  9387. var PlayToggle =
  9388. /*#__PURE__*/
  9389. function (_Button) {
  9390. _inheritsLoose(PlayToggle, _Button);
  9391. /**
  9392. * Creates an instance of this class.
  9393. *
  9394. * @param {Player} player
  9395. * The `Player` that this class should be attached to.
  9396. *
  9397. * @param {Object} [options={}]
  9398. * The key/value store of player options.
  9399. */
  9400. function PlayToggle(player, options) {
  9401. var _this;
  9402. if (options === void 0) {
  9403. options = {};
  9404. }
  9405. _this = _Button.call(this, player, options) || this; // show or hide replay icon
  9406. options.replay = options.replay === undefined || options.replay;
  9407. _this.on(player, 'play', _this.handlePlay);
  9408. _this.on(player, 'pause', _this.handlePause);
  9409. if (options.replay) {
  9410. _this.on(player, 'ended', _this.handleEnded);
  9411. }
  9412. return _this;
  9413. }
  9414. /**
  9415. * Builds the default DOM `className`.
  9416. *
  9417. * @return {string}
  9418. * The DOM `className` for this object.
  9419. */
  9420. var _proto = PlayToggle.prototype;
  9421. _proto.buildCSSClass = function buildCSSClass() {
  9422. return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
  9423. }
  9424. /**
  9425. * This gets called when an `PlayToggle` is "clicked". See
  9426. * {@link ClickableComponent} for more detailed information on what a click can be.
  9427. *
  9428. * @param {EventTarget~Event} [event]
  9429. * The `keydown`, `tap`, or `click` event that caused this function to be
  9430. * called.
  9431. *
  9432. * @listens tap
  9433. * @listens click
  9434. */
  9435. ;
  9436. _proto.handleClick = function handleClick(event) {
  9437. if (this.player_.paused()) {
  9438. this.player_.play();
  9439. } else {
  9440. this.player_.pause();
  9441. }
  9442. }
  9443. /**
  9444. * This gets called once after the video has ended and the user seeks so that
  9445. * we can change the replay button back to a play button.
  9446. *
  9447. * @param {EventTarget~Event} [event]
  9448. * The event that caused this function to run.
  9449. *
  9450. * @listens Player#seeked
  9451. */
  9452. ;
  9453. _proto.handleSeeked = function handleSeeked(event) {
  9454. this.removeClass('vjs-ended');
  9455. if (this.player_.paused()) {
  9456. this.handlePause(event);
  9457. } else {
  9458. this.handlePlay(event);
  9459. }
  9460. }
  9461. /**
  9462. * Add the vjs-playing class to the element so it can change appearance.
  9463. *
  9464. * @param {EventTarget~Event} [event]
  9465. * The event that caused this function to run.
  9466. *
  9467. * @listens Player#play
  9468. */
  9469. ;
  9470. _proto.handlePlay = function handlePlay(event) {
  9471. this.removeClass('vjs-ended');
  9472. this.removeClass('vjs-paused');
  9473. this.addClass('vjs-playing'); // change the button text to "Pause"
  9474. this.controlText('Pause');
  9475. }
  9476. /**
  9477. * Add the vjs-paused class to the element so it can change appearance.
  9478. *
  9479. * @param {EventTarget~Event} [event]
  9480. * The event that caused this function to run.
  9481. *
  9482. * @listens Player#pause
  9483. */
  9484. ;
  9485. _proto.handlePause = function handlePause(event) {
  9486. this.removeClass('vjs-playing');
  9487. this.addClass('vjs-paused'); // change the button text to "Play"
  9488. this.controlText('Play');
  9489. }
  9490. /**
  9491. * Add the vjs-ended class to the element so it can change appearance
  9492. *
  9493. * @param {EventTarget~Event} [event]
  9494. * The event that caused this function to run.
  9495. *
  9496. * @listens Player#ended
  9497. */
  9498. ;
  9499. _proto.handleEnded = function handleEnded(event) {
  9500. this.removeClass('vjs-playing');
  9501. this.addClass('vjs-ended'); // change the button text to "Replay"
  9502. this.controlText('Replay'); // on the next seek remove the replay button
  9503. this.one(this.player_, 'seeked', this.handleSeeked);
  9504. };
  9505. return PlayToggle;
  9506. }(Button);
  9507. /**
  9508. * The text that should display over the `PlayToggle`s controls. Added for localization.
  9509. *
  9510. * @type {string}
  9511. * @private
  9512. */
  9513. PlayToggle.prototype.controlText_ = 'Play';
  9514. Component.registerComponent('PlayToggle', PlayToggle);
  9515. /**
  9516. * @file format-time.js
  9517. * @module format-time
  9518. */
  9519. /**
  9520. * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
  9521. * seconds) will force a number of leading zeros to cover the length of the
  9522. * guide.
  9523. *
  9524. * @private
  9525. * @param {number} seconds
  9526. * Number of seconds to be turned into a string
  9527. *
  9528. * @param {number} guide
  9529. * Number (in seconds) to model the string after
  9530. *
  9531. * @return {string}
  9532. * Time formatted as H:MM:SS or M:SS
  9533. */
  9534. var defaultImplementation = function defaultImplementation(seconds, guide) {
  9535. seconds = seconds < 0 ? 0 : seconds;
  9536. var s = Math.floor(seconds % 60);
  9537. var m = Math.floor(seconds / 60 % 60);
  9538. var h = Math.floor(seconds / 3600);
  9539. var gm = Math.floor(guide / 60 % 60);
  9540. var gh = Math.floor(guide / 3600); // handle invalid times
  9541. if (isNaN(seconds) || seconds === Infinity) {
  9542. // '-' is false for all relational operators (e.g. <, >=) so this setting
  9543. // will add the minimum number of fields specified by the guide
  9544. h = m = s = '-';
  9545. } // Check if we need to show hours
  9546. h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
  9547. // Always show at least one digit of minutes.
  9548. m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
  9549. s = s < 10 ? '0' + s : s;
  9550. return h + m + s;
  9551. }; // Internal pointer to the current implementation.
  9552. var implementation = defaultImplementation;
  9553. /**
  9554. * Replaces the default formatTime implementation with a custom implementation.
  9555. *
  9556. * @param {Function} customImplementation
  9557. * A function which will be used in place of the default formatTime
  9558. * implementation. Will receive the current time in seconds and the
  9559. * guide (in seconds) as arguments.
  9560. */
  9561. function setFormatTime(customImplementation) {
  9562. implementation = customImplementation;
  9563. }
  9564. /**
  9565. * Resets formatTime to the default implementation.
  9566. */
  9567. function resetFormatTime() {
  9568. implementation = defaultImplementation;
  9569. }
  9570. /**
  9571. * Delegates to either the default time formatting function or a custom
  9572. * function supplied via `setFormatTime`.
  9573. *
  9574. * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
  9575. * guide (in seconds) will force a number of leading zeros to cover the
  9576. * length of the guide.
  9577. *
  9578. * @static
  9579. * @example formatTime(125, 600) === "02:05"
  9580. * @param {number} seconds
  9581. * Number of seconds to be turned into a string
  9582. *
  9583. * @param {number} guide
  9584. * Number (in seconds) to model the string after
  9585. *
  9586. * @return {string}
  9587. * Time formatted as H:MM:SS or M:SS
  9588. */
  9589. function formatTime(seconds, guide) {
  9590. if (guide === void 0) {
  9591. guide = seconds;
  9592. }
  9593. return implementation(seconds, guide);
  9594. }
  9595. /**
  9596. * Displays time information about the video
  9597. *
  9598. * @extends Component
  9599. */
  9600. var TimeDisplay =
  9601. /*#__PURE__*/
  9602. function (_Component) {
  9603. _inheritsLoose(TimeDisplay, _Component);
  9604. /**
  9605. * Creates an instance of this class.
  9606. *
  9607. * @param {Player} player
  9608. * The `Player` that this class should be attached to.
  9609. *
  9610. * @param {Object} [options]
  9611. * The key/value store of player options.
  9612. */
  9613. function TimeDisplay(player, options) {
  9614. var _this;
  9615. _this = _Component.call(this, player, options) || this;
  9616. _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateContent), 25);
  9617. _this.on(player, 'timeupdate', _this.throttledUpdateContent);
  9618. return _this;
  9619. }
  9620. /**
  9621. * Create the `Component`'s DOM element
  9622. *
  9623. * @return {Element}
  9624. * The element that was created.
  9625. */
  9626. var _proto = TimeDisplay.prototype;
  9627. _proto.createEl = function createEl$$1() {
  9628. var className = this.buildCSSClass();
  9629. var el = _Component.prototype.createEl.call(this, 'div', {
  9630. className: className + " vjs-time-control vjs-control",
  9631. innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>"
  9632. });
  9633. this.contentEl_ = createEl('span', {
  9634. className: className + "-display"
  9635. }, {
  9636. // tell screen readers not to automatically read the time as it changes
  9637. 'aria-live': 'off',
  9638. // span elements have no implicit role, but some screen readers (notably VoiceOver)
  9639. // treat them as a break between items in the DOM when using arrow keys
  9640. // (or left-to-right swipes on iOS) to read contents of a page. Using
  9641. // role='presentation' causes VoiceOver to NOT treat this span as a break.
  9642. 'role': 'presentation'
  9643. });
  9644. this.updateTextNode_();
  9645. el.appendChild(this.contentEl_);
  9646. return el;
  9647. };
  9648. _proto.dispose = function dispose() {
  9649. this.contentEl_ = null;
  9650. this.textNode_ = null;
  9651. _Component.prototype.dispose.call(this);
  9652. }
  9653. /**
  9654. * Updates the "remaining time" text node with new content using the
  9655. * contents of the `formattedTime_` property.
  9656. *
  9657. * @private
  9658. */
  9659. ;
  9660. _proto.updateTextNode_ = function updateTextNode_() {
  9661. if (!this.contentEl_) {
  9662. return;
  9663. }
  9664. while (this.contentEl_.firstChild) {
  9665. this.contentEl_.removeChild(this.contentEl_.firstChild);
  9666. }
  9667. this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0));
  9668. this.contentEl_.appendChild(this.textNode_);
  9669. }
  9670. /**
  9671. * Generates a formatted time for this component to use in display.
  9672. *
  9673. * @param {number} time
  9674. * A numeric time, in seconds.
  9675. *
  9676. * @return {string}
  9677. * A formatted time
  9678. *
  9679. * @private
  9680. */
  9681. ;
  9682. _proto.formatTime_ = function formatTime_(time) {
  9683. return formatTime(time);
  9684. }
  9685. /**
  9686. * Updates the time display text node if it has what was passed in changed
  9687. * the formatted time.
  9688. *
  9689. * @param {number} time
  9690. * The time to update to
  9691. *
  9692. * @private
  9693. */
  9694. ;
  9695. _proto.updateFormattedTime_ = function updateFormattedTime_(time) {
  9696. var formattedTime = this.formatTime_(time);
  9697. if (formattedTime === this.formattedTime_) {
  9698. return;
  9699. }
  9700. this.formattedTime_ = formattedTime;
  9701. this.requestAnimationFrame(this.updateTextNode_);
  9702. }
  9703. /**
  9704. * To be filled out in the child class, should update the displayed time
  9705. * in accordance with the fact that the current time has changed.
  9706. *
  9707. * @param {EventTarget~Event} [event]
  9708. * The `timeupdate` event that caused this to run.
  9709. *
  9710. * @listens Player#timeupdate
  9711. */
  9712. ;
  9713. _proto.updateContent = function updateContent(event) {};
  9714. return TimeDisplay;
  9715. }(Component);
  9716. /**
  9717. * The text that is added to the `TimeDisplay` for screen reader users.
  9718. *
  9719. * @type {string}
  9720. * @private
  9721. */
  9722. TimeDisplay.prototype.labelText_ = 'Time';
  9723. /**
  9724. * The text that should display over the `TimeDisplay`s controls. Added to for localization.
  9725. *
  9726. * @type {string}
  9727. * @private
  9728. *
  9729. * @deprecated in v7; controlText_ is not used in non-active display Components
  9730. */
  9731. TimeDisplay.prototype.controlText_ = 'Time';
  9732. Component.registerComponent('TimeDisplay', TimeDisplay);
  9733. /**
  9734. * Displays the current time
  9735. *
  9736. * @extends Component
  9737. */
  9738. var CurrentTimeDisplay =
  9739. /*#__PURE__*/
  9740. function (_TimeDisplay) {
  9741. _inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
  9742. /**
  9743. * Creates an instance of this class.
  9744. *
  9745. * @param {Player} player
  9746. * The `Player` that this class should be attached to.
  9747. *
  9748. * @param {Object} [options]
  9749. * The key/value store of player options.
  9750. */
  9751. function CurrentTimeDisplay(player, options) {
  9752. var _this;
  9753. _this = _TimeDisplay.call(this, player, options) || this;
  9754. _this.on(player, 'ended', _this.handleEnded);
  9755. return _this;
  9756. }
  9757. /**
  9758. * Builds the default DOM `className`.
  9759. *
  9760. * @return {string}
  9761. * The DOM `className` for this object.
  9762. */
  9763. var _proto = CurrentTimeDisplay.prototype;
  9764. _proto.buildCSSClass = function buildCSSClass() {
  9765. return 'vjs-current-time';
  9766. }
  9767. /**
  9768. * Update current time display
  9769. *
  9770. * @param {EventTarget~Event} [event]
  9771. * The `timeupdate` event that caused this function to run.
  9772. *
  9773. * @listens Player#timeupdate
  9774. */
  9775. ;
  9776. _proto.updateContent = function updateContent(event) {
  9777. // Allows for smooth scrubbing, when player can't keep up.
  9778. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  9779. this.updateFormattedTime_(time);
  9780. }
  9781. /**
  9782. * When the player fires ended there should be no time left. Sadly
  9783. * this is not always the case, lets make it seem like that is the case
  9784. * for users.
  9785. *
  9786. * @param {EventTarget~Event} [event]
  9787. * The `ended` event that caused this to run.
  9788. *
  9789. * @listens Player#ended
  9790. */
  9791. ;
  9792. _proto.handleEnded = function handleEnded(event) {
  9793. if (!this.player_.duration()) {
  9794. return;
  9795. }
  9796. this.updateFormattedTime_(this.player_.duration());
  9797. };
  9798. return CurrentTimeDisplay;
  9799. }(TimeDisplay);
  9800. /**
  9801. * The text that is added to the `CurrentTimeDisplay` for screen reader users.
  9802. *
  9803. * @type {string}
  9804. * @private
  9805. */
  9806. CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
  9807. /**
  9808. * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
  9809. *
  9810. * @type {string}
  9811. * @private
  9812. *
  9813. * @deprecated in v7; controlText_ is not used in non-active display Components
  9814. */
  9815. CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
  9816. Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
  9817. /**
  9818. * Displays the duration
  9819. *
  9820. * @extends Component
  9821. */
  9822. var DurationDisplay =
  9823. /*#__PURE__*/
  9824. function (_TimeDisplay) {
  9825. _inheritsLoose(DurationDisplay, _TimeDisplay);
  9826. /**
  9827. * Creates an instance of this class.
  9828. *
  9829. * @param {Player} player
  9830. * The `Player` that this class should be attached to.
  9831. *
  9832. * @param {Object} [options]
  9833. * The key/value store of player options.
  9834. */
  9835. function DurationDisplay(player, options) {
  9836. var _this;
  9837. _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes,
  9838. // as they should always display the changed duration as
  9839. // it has changed
  9840. _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
  9841. // but the durationchange on the user agent will not fire.
  9842. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  9843. _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
  9844. // listeners could have broken dependent applications/libraries. These
  9845. // can likely be removed for 7.0.
  9846. _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
  9847. return _this;
  9848. }
  9849. /**
  9850. * Builds the default DOM `className`.
  9851. *
  9852. * @return {string}
  9853. * The DOM `className` for this object.
  9854. */
  9855. var _proto = DurationDisplay.prototype;
  9856. _proto.buildCSSClass = function buildCSSClass() {
  9857. return 'vjs-duration';
  9858. }
  9859. /**
  9860. * Update duration time display.
  9861. *
  9862. * @param {EventTarget~Event} [event]
  9863. * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
  9864. * this function to be called.
  9865. *
  9866. * @listens Player#durationchange
  9867. * @listens Player#timeupdate
  9868. * @listens Player#loadedmetadata
  9869. */
  9870. ;
  9871. _proto.updateContent = function updateContent(event) {
  9872. var duration = this.player_.duration();
  9873. if (this.duration_ !== duration) {
  9874. this.duration_ = duration;
  9875. this.updateFormattedTime_(duration);
  9876. }
  9877. };
  9878. return DurationDisplay;
  9879. }(TimeDisplay);
  9880. /**
  9881. * The text that is added to the `DurationDisplay` for screen reader users.
  9882. *
  9883. * @type {string}
  9884. * @private
  9885. */
  9886. DurationDisplay.prototype.labelText_ = 'Duration';
  9887. /**
  9888. * The text that should display over the `DurationDisplay`s controls. Added to for localization.
  9889. *
  9890. * @type {string}
  9891. * @private
  9892. *
  9893. * @deprecated in v7; controlText_ is not used in non-active display Components
  9894. */
  9895. DurationDisplay.prototype.controlText_ = 'Duration';
  9896. Component.registerComponent('DurationDisplay', DurationDisplay);
  9897. /**
  9898. * The separator between the current time and duration.
  9899. * Can be hidden if it's not needed in the design.
  9900. *
  9901. * @extends Component
  9902. */
  9903. var TimeDivider =
  9904. /*#__PURE__*/
  9905. function (_Component) {
  9906. _inheritsLoose(TimeDivider, _Component);
  9907. function TimeDivider() {
  9908. return _Component.apply(this, arguments) || this;
  9909. }
  9910. var _proto = TimeDivider.prototype;
  9911. /**
  9912. * Create the component's DOM element
  9913. *
  9914. * @return {Element}
  9915. * The element that was created.
  9916. */
  9917. _proto.createEl = function createEl() {
  9918. return _Component.prototype.createEl.call(this, 'div', {
  9919. className: 'vjs-time-control vjs-time-divider',
  9920. innerHTML: '<div><span>/</span></div>'
  9921. }, {
  9922. // this element and its contents can be hidden from assistive techs since
  9923. // it is made extraneous by the announcement of the control text
  9924. // for the current time and duration displays
  9925. 'aria-hidden': true
  9926. });
  9927. };
  9928. return TimeDivider;
  9929. }(Component);
  9930. Component.registerComponent('TimeDivider', TimeDivider);
  9931. /**
  9932. * Displays the time left in the video
  9933. *
  9934. * @extends Component
  9935. */
  9936. var RemainingTimeDisplay =
  9937. /*#__PURE__*/
  9938. function (_TimeDisplay) {
  9939. _inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
  9940. /**
  9941. * Creates an instance of this class.
  9942. *
  9943. * @param {Player} player
  9944. * The `Player` that this class should be attached to.
  9945. *
  9946. * @param {Object} [options]
  9947. * The key/value store of player options.
  9948. */
  9949. function RemainingTimeDisplay(player, options) {
  9950. var _this;
  9951. _this = _TimeDisplay.call(this, player, options) || this;
  9952. _this.on(player, 'durationchange', _this.throttledUpdateContent);
  9953. _this.on(player, 'ended', _this.handleEnded);
  9954. return _this;
  9955. }
  9956. /**
  9957. * Builds the default DOM `className`.
  9958. *
  9959. * @return {string}
  9960. * The DOM `className` for this object.
  9961. */
  9962. var _proto = RemainingTimeDisplay.prototype;
  9963. _proto.buildCSSClass = function buildCSSClass() {
  9964. return 'vjs-remaining-time';
  9965. }
  9966. /**
  9967. * Create the `Component`'s DOM element with the "minus" characted prepend to the time
  9968. *
  9969. * @return {Element}
  9970. * The element that was created.
  9971. */
  9972. ;
  9973. _proto.createEl = function createEl$$1() {
  9974. var el = _TimeDisplay.prototype.createEl.call(this);
  9975. el.insertBefore(createEl('span', {}, {
  9976. 'aria-hidden': true
  9977. }, '-'), this.contentEl_);
  9978. return el;
  9979. }
  9980. /**
  9981. * Update remaining time display.
  9982. *
  9983. * @param {EventTarget~Event} [event]
  9984. * The `timeupdate` or `durationchange` event that caused this to run.
  9985. *
  9986. * @listens Player#timeupdate
  9987. * @listens Player#durationchange
  9988. */
  9989. ;
  9990. _proto.updateContent = function updateContent(event) {
  9991. if (typeof this.player_.duration() !== 'number') {
  9992. return;
  9993. } // @deprecated We should only use remainingTimeDisplay
  9994. // as of video.js 7
  9995. if (this.player_.remainingTimeDisplay) {
  9996. this.updateFormattedTime_(this.player_.remainingTimeDisplay());
  9997. } else {
  9998. this.updateFormattedTime_(this.player_.remainingTime());
  9999. }
  10000. }
  10001. /**
  10002. * When the player fires ended there should be no time left. Sadly
  10003. * this is not always the case, lets make it seem like that is the case
  10004. * for users.
  10005. *
  10006. * @param {EventTarget~Event} [event]
  10007. * The `ended` event that caused this to run.
  10008. *
  10009. * @listens Player#ended
  10010. */
  10011. ;
  10012. _proto.handleEnded = function handleEnded(event) {
  10013. if (!this.player_.duration()) {
  10014. return;
  10015. }
  10016. this.updateFormattedTime_(0);
  10017. };
  10018. return RemainingTimeDisplay;
  10019. }(TimeDisplay);
  10020. /**
  10021. * The text that is added to the `RemainingTimeDisplay` for screen reader users.
  10022. *
  10023. * @type {string}
  10024. * @private
  10025. */
  10026. RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
  10027. /**
  10028. * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
  10029. *
  10030. * @type {string}
  10031. * @private
  10032. *
  10033. * @deprecated in v7; controlText_ is not used in non-active display Components
  10034. */
  10035. RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
  10036. Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
  10037. /**
  10038. * Displays the live indicator when duration is Infinity.
  10039. *
  10040. * @extends Component
  10041. */
  10042. var LiveDisplay =
  10043. /*#__PURE__*/
  10044. function (_Component) {
  10045. _inheritsLoose(LiveDisplay, _Component);
  10046. /**
  10047. * Creates an instance of this class.
  10048. *
  10049. * @param {Player} player
  10050. * The `Player` that this class should be attached to.
  10051. *
  10052. * @param {Object} [options]
  10053. * The key/value store of player options.
  10054. */
  10055. function LiveDisplay(player, options) {
  10056. var _this;
  10057. _this = _Component.call(this, player, options) || this;
  10058. _this.updateShowing();
  10059. _this.on(_this.player(), 'durationchange', _this.updateShowing);
  10060. return _this;
  10061. }
  10062. /**
  10063. * Create the `Component`'s DOM element
  10064. *
  10065. * @return {Element}
  10066. * The element that was created.
  10067. */
  10068. var _proto = LiveDisplay.prototype;
  10069. _proto.createEl = function createEl$$1() {
  10070. var el = _Component.prototype.createEl.call(this, 'div', {
  10071. className: 'vjs-live-control vjs-control'
  10072. });
  10073. this.contentEl_ = createEl('div', {
  10074. className: 'vjs-live-display',
  10075. innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE')
  10076. }, {
  10077. 'aria-live': 'off'
  10078. });
  10079. el.appendChild(this.contentEl_);
  10080. return el;
  10081. };
  10082. _proto.dispose = function dispose() {
  10083. this.contentEl_ = null;
  10084. _Component.prototype.dispose.call(this);
  10085. }
  10086. /**
  10087. * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
  10088. * it accordingly
  10089. *
  10090. * @param {EventTarget~Event} [event]
  10091. * The {@link Player#durationchange} event that caused this function to run.
  10092. *
  10093. * @listens Player#durationchange
  10094. */
  10095. ;
  10096. _proto.updateShowing = function updateShowing(event) {
  10097. if (this.player().duration() === Infinity) {
  10098. this.show();
  10099. } else {
  10100. this.hide();
  10101. }
  10102. };
  10103. return LiveDisplay;
  10104. }(Component);
  10105. Component.registerComponent('LiveDisplay', LiveDisplay);
  10106. /**
  10107. * Displays the live indicator when duration is Infinity.
  10108. *
  10109. * @extends Component
  10110. */
  10111. var SeekToLive =
  10112. /*#__PURE__*/
  10113. function (_Button) {
  10114. _inheritsLoose(SeekToLive, _Button);
  10115. /**
  10116. * Creates an instance of this class.
  10117. *
  10118. * @param {Player} player
  10119. * The `Player` that this class should be attached to.
  10120. *
  10121. * @param {Object} [options]
  10122. * The key/value store of player options.
  10123. */
  10124. function SeekToLive(player, options) {
  10125. var _this;
  10126. _this = _Button.call(this, player, options) || this;
  10127. _this.updateLiveEdgeStatus();
  10128. if (_this.player_.liveTracker) {
  10129. _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus);
  10130. }
  10131. return _this;
  10132. }
  10133. /**
  10134. * Create the `Component`'s DOM element
  10135. *
  10136. * @return {Element}
  10137. * The element that was created.
  10138. */
  10139. var _proto = SeekToLive.prototype;
  10140. _proto.createEl = function createEl$$1() {
  10141. var el = _Button.prototype.createEl.call(this, 'button', {
  10142. className: 'vjs-seek-to-live-control vjs-control'
  10143. });
  10144. this.textEl_ = createEl('span', {
  10145. className: 'vjs-seek-to-live-text',
  10146. innerHTML: this.localize('LIVE')
  10147. }, {
  10148. 'aria-hidden': 'true'
  10149. });
  10150. el.appendChild(this.textEl_);
  10151. return el;
  10152. }
  10153. /**
  10154. * Update the state of this button if we are at the live edge
  10155. * or not
  10156. */
  10157. ;
  10158. _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) {
  10159. // default to live edge
  10160. if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
  10161. this.setAttribute('aria-disabled', true);
  10162. this.addClass('vjs-at-live-edge');
  10163. this.controlText('Seek to live, currently playing live');
  10164. } else {
  10165. this.setAttribute('aria-disabled', false);
  10166. this.removeClass('vjs-at-live-edge');
  10167. this.controlText('Seek to live, currently behind live');
  10168. }
  10169. }
  10170. /**
  10171. * On click bring us as near to the live point as possible.
  10172. * This requires that we wait for the next `live-seekable-change`
  10173. * event which will happen every segment length seconds.
  10174. */
  10175. ;
  10176. _proto.handleClick = function handleClick() {
  10177. this.player_.liveTracker.seekToLiveEdge();
  10178. }
  10179. /**
  10180. * Dispose of the element and stop tracking
  10181. */
  10182. ;
  10183. _proto.dispose = function dispose() {
  10184. if (this.player_.liveTracker) {
  10185. this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus);
  10186. }
  10187. this.textEl_ = null;
  10188. _Button.prototype.dispose.call(this);
  10189. };
  10190. return SeekToLive;
  10191. }(Button);
  10192. SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
  10193. Component.registerComponent('SeekToLive', SeekToLive);
  10194. /**
  10195. * The base functionality for a slider. Can be vertical or horizontal.
  10196. * For instance the volume bar or the seek bar on a video is a slider.
  10197. *
  10198. * @extends Component
  10199. */
  10200. var Slider =
  10201. /*#__PURE__*/
  10202. function (_Component) {
  10203. _inheritsLoose(Slider, _Component);
  10204. /**
  10205. * Create an instance of this class
  10206. *
  10207. * @param {Player} player
  10208. * The `Player` that this class should be attached to.
  10209. *
  10210. * @param {Object} [options]
  10211. * The key/value store of player options.
  10212. */
  10213. function Slider(player, options) {
  10214. var _this;
  10215. _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for
  10216. _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
  10217. _this.vertical(!!_this.options_.vertical);
  10218. _this.enable();
  10219. return _this;
  10220. }
  10221. /**
  10222. * Are controls are currently enabled for this slider or not.
  10223. *
  10224. * @return {boolean}
  10225. * true if controls are enabled, false otherwise
  10226. */
  10227. var _proto = Slider.prototype;
  10228. _proto.enabled = function enabled() {
  10229. return this.enabled_;
  10230. }
  10231. /**
  10232. * Enable controls for this slider if they are disabled
  10233. */
  10234. ;
  10235. _proto.enable = function enable() {
  10236. if (this.enabled()) {
  10237. return;
  10238. }
  10239. this.on('mousedown', this.handleMouseDown);
  10240. this.on('touchstart', this.handleMouseDown);
  10241. this.on('focus', this.handleFocus);
  10242. this.on('blur', this.handleBlur);
  10243. this.on('click', this.handleClick);
  10244. this.on(this.player_, 'controlsvisible', this.update);
  10245. if (this.playerEvent) {
  10246. this.on(this.player_, this.playerEvent, this.update);
  10247. }
  10248. this.removeClass('disabled');
  10249. this.setAttribute('tabindex', 0);
  10250. this.enabled_ = true;
  10251. }
  10252. /**
  10253. * Disable controls for this slider if they are enabled
  10254. */
  10255. ;
  10256. _proto.disable = function disable() {
  10257. if (!this.enabled()) {
  10258. return;
  10259. }
  10260. var doc = this.bar.el_.ownerDocument;
  10261. this.off('mousedown', this.handleMouseDown);
  10262. this.off('touchstart', this.handleMouseDown);
  10263. this.off('focus', this.handleFocus);
  10264. this.off('blur', this.handleBlur);
  10265. this.off('click', this.handleClick);
  10266. this.off(this.player_, 'controlsvisible', this.update);
  10267. this.off(doc, 'mousemove', this.handleMouseMove);
  10268. this.off(doc, 'mouseup', this.handleMouseUp);
  10269. this.off(doc, 'touchmove', this.handleMouseMove);
  10270. this.off(doc, 'touchend', this.handleMouseUp);
  10271. this.removeAttribute('tabindex');
  10272. this.addClass('disabled');
  10273. if (this.playerEvent) {
  10274. this.off(this.player_, this.playerEvent, this.update);
  10275. }
  10276. this.enabled_ = false;
  10277. }
  10278. /**
  10279. * Create the `Slider`s DOM element.
  10280. *
  10281. * @param {string} type
  10282. * Type of element to create.
  10283. *
  10284. * @param {Object} [props={}]
  10285. * List of properties in Object form.
  10286. *
  10287. * @param {Object} [attributes={}]
  10288. * list of attributes in Object form.
  10289. *
  10290. * @return {Element}
  10291. * The element that gets created.
  10292. */
  10293. ;
  10294. _proto.createEl = function createEl$$1(type, props, attributes) {
  10295. if (props === void 0) {
  10296. props = {};
  10297. }
  10298. if (attributes === void 0) {
  10299. attributes = {};
  10300. }
  10301. // Add the slider element class to all sub classes
  10302. props.className = props.className + ' vjs-slider';
  10303. props = assign({
  10304. tabIndex: 0
  10305. }, props);
  10306. attributes = assign({
  10307. 'role': 'slider',
  10308. 'aria-valuenow': 0,
  10309. 'aria-valuemin': 0,
  10310. 'aria-valuemax': 100,
  10311. 'tabIndex': 0
  10312. }, attributes);
  10313. return _Component.prototype.createEl.call(this, type, props, attributes);
  10314. }
  10315. /**
  10316. * Handle `mousedown` or `touchstart` events on the `Slider`.
  10317. *
  10318. * @param {EventTarget~Event} event
  10319. * `mousedown` or `touchstart` event that triggered this function
  10320. *
  10321. * @listens mousedown
  10322. * @listens touchstart
  10323. * @fires Slider#slideractive
  10324. */
  10325. ;
  10326. _proto.handleMouseDown = function handleMouseDown(event) {
  10327. var doc = this.bar.el_.ownerDocument;
  10328. if (event.type === 'mousedown') {
  10329. event.preventDefault();
  10330. } // Do not call preventDefault() on touchstart in Chrome
  10331. // to avoid console warnings. Use a 'touch-action: none' style
  10332. // instead to prevent unintented scrolling.
  10333. // https://developers.google.com/web/updates/2017/01/scrolling-intervention
  10334. if (event.type === 'touchstart' && !IS_CHROME) {
  10335. event.preventDefault();
  10336. }
  10337. blockTextSelection();
  10338. this.addClass('vjs-sliding');
  10339. /**
  10340. * Triggered when the slider is in an active state
  10341. *
  10342. * @event Slider#slideractive
  10343. * @type {EventTarget~Event}
  10344. */
  10345. this.trigger('slideractive');
  10346. this.on(doc, 'mousemove', this.handleMouseMove);
  10347. this.on(doc, 'mouseup', this.handleMouseUp);
  10348. this.on(doc, 'touchmove', this.handleMouseMove);
  10349. this.on(doc, 'touchend', this.handleMouseUp);
  10350. this.handleMouseMove(event);
  10351. }
  10352. /**
  10353. * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
  10354. * The `mousemove` and `touchmove` events will only only trigger this function during
  10355. * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
  10356. * {@link Slider#handleMouseUp}.
  10357. *
  10358. * @param {EventTarget~Event} event
  10359. * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
  10360. * this function
  10361. *
  10362. * @listens mousemove
  10363. * @listens touchmove
  10364. */
  10365. ;
  10366. _proto.handleMouseMove = function handleMouseMove(event) {}
  10367. /**
  10368. * Handle `mouseup` or `touchend` events on the `Slider`.
  10369. *
  10370. * @param {EventTarget~Event} event
  10371. * `mouseup` or `touchend` event that triggered this function.
  10372. *
  10373. * @listens touchend
  10374. * @listens mouseup
  10375. * @fires Slider#sliderinactive
  10376. */
  10377. ;
  10378. _proto.handleMouseUp = function handleMouseUp() {
  10379. var doc = this.bar.el_.ownerDocument;
  10380. unblockTextSelection();
  10381. this.removeClass('vjs-sliding');
  10382. /**
  10383. * Triggered when the slider is no longer in an active state.
  10384. *
  10385. * @event Slider#sliderinactive
  10386. * @type {EventTarget~Event}
  10387. */
  10388. this.trigger('sliderinactive');
  10389. this.off(doc, 'mousemove', this.handleMouseMove);
  10390. this.off(doc, 'mouseup', this.handleMouseUp);
  10391. this.off(doc, 'touchmove', this.handleMouseMove);
  10392. this.off(doc, 'touchend', this.handleMouseUp);
  10393. this.update();
  10394. }
  10395. /**
  10396. * Update the progress bar of the `Slider`.
  10397. *
  10398. * @return {number}
  10399. * The percentage of progress the progress bar represents as a
  10400. * number from 0 to 1.
  10401. */
  10402. ;
  10403. _proto.update = function update() {
  10404. // In VolumeBar init we have a setTimeout for update that pops and update
  10405. // to the end of the execution stack. The player is destroyed before then
  10406. // update will cause an error
  10407. if (!this.el_) {
  10408. return;
  10409. } // If scrubbing, we could use a cached value to make the handle keep up
  10410. // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
  10411. // some flash players are slow, so we might want to utilize this later.
  10412. // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  10413. var progress = this.getPercent();
  10414. var bar = this.bar; // If there's no bar...
  10415. if (!bar) {
  10416. return;
  10417. } // Protect against no duration and other division issues
  10418. if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
  10419. progress = 0;
  10420. } // Convert to a percentage for setting
  10421. var percentage = (progress * 100).toFixed(2) + '%';
  10422. var style = bar.el().style; // Set the new bar width or height
  10423. if (this.vertical()) {
  10424. style.height = percentage;
  10425. } else {
  10426. style.width = percentage;
  10427. }
  10428. return progress;
  10429. }
  10430. /**
  10431. * Calculate distance for slider
  10432. *
  10433. * @param {EventTarget~Event} event
  10434. * The event that caused this function to run.
  10435. *
  10436. * @return {number}
  10437. * The current position of the Slider.
  10438. * - position.x for vertical `Slider`s
  10439. * - position.y for horizontal `Slider`s
  10440. */
  10441. ;
  10442. _proto.calculateDistance = function calculateDistance(event) {
  10443. var position = getPointerPosition(this.el_, event);
  10444. if (this.vertical()) {
  10445. return position.y;
  10446. }
  10447. return position.x;
  10448. }
  10449. /**
  10450. * Handle a `focus` event on this `Slider`.
  10451. *
  10452. * @param {EventTarget~Event} event
  10453. * The `focus` event that caused this function to run.
  10454. *
  10455. * @listens focus
  10456. */
  10457. ;
  10458. _proto.handleFocus = function handleFocus() {
  10459. this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  10460. }
  10461. /**
  10462. * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
  10463. * arrow keys. This function will only be called when the slider has focus. See
  10464. * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
  10465. *
  10466. * @param {EventTarget~Event} event
  10467. * the `keydown` event that caused this function to run.
  10468. *
  10469. * @listens keydown
  10470. */
  10471. ;
  10472. _proto.handleKeyPress = function handleKeyPress(event) {
  10473. // Left and Down Arrows
  10474. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  10475. event.preventDefault();
  10476. this.stepBack(); // Up and Right Arrows
  10477. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  10478. event.preventDefault();
  10479. this.stepForward();
  10480. } else {
  10481. // Pass keypress handling up for unsupported keys
  10482. _Component.prototype.handleKeyPress.call(this, event);
  10483. }
  10484. }
  10485. /**
  10486. * Handle a `blur` event on this `Slider`.
  10487. *
  10488. * @param {EventTarget~Event} event
  10489. * The `blur` event that caused this function to run.
  10490. *
  10491. * @listens blur
  10492. */
  10493. ;
  10494. _proto.handleBlur = function handleBlur() {
  10495. this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  10496. }
  10497. /**
  10498. * Listener for click events on slider, used to prevent clicks
  10499. * from bubbling up to parent elements like button menus.
  10500. *
  10501. * @param {Object} event
  10502. * Event that caused this object to run
  10503. */
  10504. ;
  10505. _proto.handleClick = function handleClick(event) {
  10506. event.stopImmediatePropagation();
  10507. event.preventDefault();
  10508. }
  10509. /**
  10510. * Get/set if slider is horizontal for vertical
  10511. *
  10512. * @param {boolean} [bool]
  10513. * - true if slider is vertical,
  10514. * - false is horizontal
  10515. *
  10516. * @return {boolean}
  10517. * - true if slider is vertical, and getting
  10518. * - false if the slider is horizontal, and getting
  10519. */
  10520. ;
  10521. _proto.vertical = function vertical(bool) {
  10522. if (bool === undefined) {
  10523. return this.vertical_ || false;
  10524. }
  10525. this.vertical_ = !!bool;
  10526. if (this.vertical_) {
  10527. this.addClass('vjs-slider-vertical');
  10528. } else {
  10529. this.addClass('vjs-slider-horizontal');
  10530. }
  10531. };
  10532. return Slider;
  10533. }(Component);
  10534. Component.registerComponent('Slider', Slider);
  10535. /**
  10536. * Shows loading progress
  10537. *
  10538. * @extends Component
  10539. */
  10540. var LoadProgressBar =
  10541. /*#__PURE__*/
  10542. function (_Component) {
  10543. _inheritsLoose(LoadProgressBar, _Component);
  10544. /**
  10545. * Creates an instance of this class.
  10546. *
  10547. * @param {Player} player
  10548. * The `Player` that this class should be attached to.
  10549. *
  10550. * @param {Object} [options]
  10551. * The key/value store of player options.
  10552. */
  10553. function LoadProgressBar(player, options) {
  10554. var _this;
  10555. _this = _Component.call(this, player, options) || this;
  10556. _this.partEls_ = [];
  10557. _this.on(player, 'progress', _this.update);
  10558. return _this;
  10559. }
  10560. /**
  10561. * Create the `Component`'s DOM element
  10562. *
  10563. * @return {Element}
  10564. * The element that was created.
  10565. */
  10566. var _proto = LoadProgressBar.prototype;
  10567. _proto.createEl = function createEl$$1() {
  10568. return _Component.prototype.createEl.call(this, 'div', {
  10569. className: 'vjs-load-progress',
  10570. innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>"
  10571. });
  10572. };
  10573. _proto.dispose = function dispose() {
  10574. this.partEls_ = null;
  10575. _Component.prototype.dispose.call(this);
  10576. }
  10577. /**
  10578. * Update progress bar
  10579. *
  10580. * @param {EventTarget~Event} [event]
  10581. * The `progress` event that caused this function to run.
  10582. *
  10583. * @listens Player#progress
  10584. */
  10585. ;
  10586. _proto.update = function update(event) {
  10587. var liveTracker = this.player_.liveTracker;
  10588. var buffered = this.player_.buffered();
  10589. var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
  10590. var bufferedEnd = this.player_.bufferedEnd();
  10591. var children = this.partEls_;
  10592. var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end
  10593. var percentify = function percentify(time, end, rounded) {
  10594. // no NaN
  10595. var percent = time / end || 0;
  10596. percent = (percent >= 1 ? 1 : percent) * 100;
  10597. if (rounded) {
  10598. percent = percent.toFixed(2);
  10599. }
  10600. return percent + '%';
  10601. }; // update the width of the progress bar
  10602. this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text
  10603. textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges
  10604. for (var i = 0; i < buffered.length; i++) {
  10605. var start = buffered.start(i);
  10606. var end = buffered.end(i);
  10607. var part = children[i];
  10608. if (!part) {
  10609. part = this.el_.appendChild(createEl());
  10610. children[i] = part;
  10611. } // set the percent based on the width of the progress bar (bufferedEnd)
  10612. part.style.left = percentify(start, bufferedEnd);
  10613. part.style.width = percentify(end - start, bufferedEnd);
  10614. } // remove unused buffered range elements
  10615. for (var _i = children.length; _i > buffered.length; _i--) {
  10616. this.el_.removeChild(children[_i - 1]);
  10617. }
  10618. children.length = buffered.length;
  10619. };
  10620. return LoadProgressBar;
  10621. }(Component);
  10622. Component.registerComponent('LoadProgressBar', LoadProgressBar);
  10623. /**
  10624. * Time tooltips display a time above the progress bar.
  10625. *
  10626. * @extends Component
  10627. */
  10628. var TimeTooltip =
  10629. /*#__PURE__*/
  10630. function (_Component) {
  10631. _inheritsLoose(TimeTooltip, _Component);
  10632. function TimeTooltip() {
  10633. return _Component.apply(this, arguments) || this;
  10634. }
  10635. var _proto = TimeTooltip.prototype;
  10636. /**
  10637. * Create the time tooltip DOM element
  10638. *
  10639. * @return {Element}
  10640. * The element that was created.
  10641. */
  10642. _proto.createEl = function createEl$$1() {
  10643. return _Component.prototype.createEl.call(this, 'div', {
  10644. className: 'vjs-time-tooltip'
  10645. }, {
  10646. 'aria-hidden': 'true'
  10647. });
  10648. }
  10649. /**
  10650. * Updates the position of the time tooltip relative to the `SeekBar`.
  10651. *
  10652. * @param {Object} seekBarRect
  10653. * The `ClientRect` for the {@link SeekBar} element.
  10654. *
  10655. * @param {number} seekBarPoint
  10656. * A number from 0 to 1, representing a horizontal reference point
  10657. * from the left edge of the {@link SeekBar}
  10658. */
  10659. ;
  10660. _proto.update = function update(seekBarRect, seekBarPoint, content) {
  10661. var tooltipRect = getBoundingClientRect(this.el_);
  10662. var playerRect = getBoundingClientRect(this.player_.el());
  10663. var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
  10664. // for example, if the player isn't in the DOM for testing
  10665. if (!playerRect || !tooltipRect) {
  10666. return;
  10667. } // This is the space left of the `seekBarPoint` available within the bounds
  10668. // of the player. We calculate any gap between the left edge of the player
  10669. // and the left edge of the `SeekBar` and add the number of pixels in the
  10670. // `SeekBar` before hitting the `seekBarPoint`
  10671. var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
  10672. // of the player. We calculate the number of pixels from the `seekBarPoint`
  10673. // to the right edge of the `SeekBar` and add to that any gap between the
  10674. // right edge of the `SeekBar` and the player.
  10675. var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
  10676. // further to the right to center it over the `seekBarPoint`.
  10677. var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
  10678. // the results of the space calculations above.
  10679. if (spaceLeftOfPoint < pullTooltipBy) {
  10680. pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  10681. } else if (spaceRightOfPoint < pullTooltipBy) {
  10682. pullTooltipBy = spaceRightOfPoint;
  10683. } // Due to the imprecision of decimal/ratio based calculations and varying
  10684. // rounding behaviors, there are cases where the spacing adjustment is off
  10685. // by a pixel or two. This adds insurance to these calculations.
  10686. if (pullTooltipBy < 0) {
  10687. pullTooltipBy = 0;
  10688. } else if (pullTooltipBy > tooltipRect.width) {
  10689. pullTooltipBy = tooltipRect.width;
  10690. }
  10691. this.el_.style.right = "-" + pullTooltipBy + "px";
  10692. textContent(this.el_, content);
  10693. }
  10694. /**
  10695. * Updates the position of the time tooltip relative to the `SeekBar`.
  10696. *
  10697. * @param {Object} seekBarRect
  10698. * The `ClientRect` for the {@link SeekBar} element.
  10699. *
  10700. * @param {number} seekBarPoint
  10701. * A number from 0 to 1, representing a horizontal reference point
  10702. * from the left edge of the {@link SeekBar}
  10703. *
  10704. * @param {number} time
  10705. * The time to update the tooltip to, not used during live playback
  10706. *
  10707. * @param {Function} cb
  10708. * A function that will be called during the request animation frame
  10709. * for tooltips that need to do additional animations from the default
  10710. */
  10711. ;
  10712. _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
  10713. var _this = this;
  10714. // If there is an existing rAF ID, cancel it so we don't over-queue.
  10715. if (this.rafId_) {
  10716. this.cancelAnimationFrame(this.rafId_);
  10717. }
  10718. this.rafId_ = this.requestAnimationFrame(function () {
  10719. var content;
  10720. var duration = _this.player_.duration();
  10721. if (_this.player_.liveTracker && _this.player_.liveTracker.isLive()) {
  10722. var liveWindow = _this.player_.liveTracker.liveWindow();
  10723. var secondsBehind = liveWindow - seekBarPoint * liveWindow;
  10724. content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
  10725. } else {
  10726. content = formatTime(time, duration);
  10727. }
  10728. _this.update(seekBarRect, seekBarPoint, content);
  10729. if (cb) {
  10730. cb();
  10731. }
  10732. });
  10733. };
  10734. return TimeTooltip;
  10735. }(Component);
  10736. Component.registerComponent('TimeTooltip', TimeTooltip);
  10737. /**
  10738. * Used by {@link SeekBar} to display media playback progress as part of the
  10739. * {@link ProgressControl}.
  10740. *
  10741. * @extends Component
  10742. */
  10743. var PlayProgressBar =
  10744. /*#__PURE__*/
  10745. function (_Component) {
  10746. _inheritsLoose(PlayProgressBar, _Component);
  10747. function PlayProgressBar() {
  10748. return _Component.apply(this, arguments) || this;
  10749. }
  10750. var _proto = PlayProgressBar.prototype;
  10751. /**
  10752. * Create the the DOM element for this class.
  10753. *
  10754. * @return {Element}
  10755. * The element that was created.
  10756. */
  10757. _proto.createEl = function createEl() {
  10758. return _Component.prototype.createEl.call(this, 'div', {
  10759. className: 'vjs-play-progress vjs-slider-bar'
  10760. }, {
  10761. 'aria-hidden': 'true'
  10762. });
  10763. }
  10764. /**
  10765. * Enqueues updates to its own DOM as well as the DOM of its
  10766. * {@link TimeTooltip} child.
  10767. *
  10768. * @param {Object} seekBarRect
  10769. * The `ClientRect` for the {@link SeekBar} element.
  10770. *
  10771. * @param {number} seekBarPoint
  10772. * A number from 0 to 1, representing a horizontal reference point
  10773. * from the left edge of the {@link SeekBar}
  10774. */
  10775. ;
  10776. _proto.update = function update(seekBarRect, seekBarPoint) {
  10777. var timeTooltip = this.getChild('timeTooltip');
  10778. if (!timeTooltip) {
  10779. return;
  10780. }
  10781. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  10782. timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
  10783. };
  10784. return PlayProgressBar;
  10785. }(Component);
  10786. /**
  10787. * Default options for {@link PlayProgressBar}.
  10788. *
  10789. * @type {Object}
  10790. * @private
  10791. */
  10792. PlayProgressBar.prototype.options_ = {
  10793. children: []
  10794. }; // Time tooltips should not be added to a player on mobile devices
  10795. if (!IS_IOS && !IS_ANDROID) {
  10796. PlayProgressBar.prototype.options_.children.push('timeTooltip');
  10797. }
  10798. Component.registerComponent('PlayProgressBar', PlayProgressBar);
  10799. /**
  10800. * The {@link MouseTimeDisplay} component tracks mouse movement over the
  10801. * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
  10802. * indicating the time which is represented by a given point in the
  10803. * {@link ProgressControl}.
  10804. *
  10805. * @extends Component
  10806. */
  10807. var MouseTimeDisplay =
  10808. /*#__PURE__*/
  10809. function (_Component) {
  10810. _inheritsLoose(MouseTimeDisplay, _Component);
  10811. /**
  10812. * Creates an instance of this class.
  10813. *
  10814. * @param {Player} player
  10815. * The {@link Player} that this class should be attached to.
  10816. *
  10817. * @param {Object} [options]
  10818. * The key/value store of player options.
  10819. */
  10820. function MouseTimeDisplay(player, options) {
  10821. var _this;
  10822. _this = _Component.call(this, player, options) || this;
  10823. _this.update = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update), 25);
  10824. return _this;
  10825. }
  10826. /**
  10827. * Create the DOM element for this class.
  10828. *
  10829. * @return {Element}
  10830. * The element that was created.
  10831. */
  10832. var _proto = MouseTimeDisplay.prototype;
  10833. _proto.createEl = function createEl() {
  10834. return _Component.prototype.createEl.call(this, 'div', {
  10835. className: 'vjs-mouse-display'
  10836. });
  10837. }
  10838. /**
  10839. * Enqueues updates to its own DOM as well as the DOM of its
  10840. * {@link TimeTooltip} child.
  10841. *
  10842. * @param {Object} seekBarRect
  10843. * The `ClientRect` for the {@link SeekBar} element.
  10844. *
  10845. * @param {number} seekBarPoint
  10846. * A number from 0 to 1, representing a horizontal reference point
  10847. * from the left edge of the {@link SeekBar}
  10848. */
  10849. ;
  10850. _proto.update = function update(seekBarRect, seekBarPoint) {
  10851. var _this2 = this;
  10852. var time = seekBarPoint * this.player_.duration();
  10853. this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
  10854. _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
  10855. });
  10856. };
  10857. return MouseTimeDisplay;
  10858. }(Component);
  10859. /**
  10860. * Default options for `MouseTimeDisplay`
  10861. *
  10862. * @type {Object}
  10863. * @private
  10864. */
  10865. MouseTimeDisplay.prototype.options_ = {
  10866. children: ['timeTooltip']
  10867. };
  10868. Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
  10869. var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
  10870. var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses.
  10871. var UPDATE_REFRESH_INTERVAL = 30;
  10872. /**
  10873. * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
  10874. * as its `bar`.
  10875. *
  10876. * @extends Slider
  10877. */
  10878. var SeekBar =
  10879. /*#__PURE__*/
  10880. function (_Slider) {
  10881. _inheritsLoose(SeekBar, _Slider);
  10882. /**
  10883. * Creates an instance of this class.
  10884. *
  10885. * @param {Player} player
  10886. * The `Player` that this class should be attached to.
  10887. *
  10888. * @param {Object} [options]
  10889. * The key/value store of player options.
  10890. */
  10891. function SeekBar(player, options) {
  10892. var _this;
  10893. _this = _Slider.call(this, player, options) || this;
  10894. _this.setEventHandlers_();
  10895. return _this;
  10896. }
  10897. /**
  10898. * Sets the event handlers
  10899. *
  10900. * @private
  10901. */
  10902. var _proto = SeekBar.prototype;
  10903. _proto.setEventHandlers_ = function setEventHandlers_() {
  10904. this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
  10905. this.on(this.player_, 'timeupdate', this.update);
  10906. this.on(this.player_, 'ended', this.handleEnded);
  10907. this.on(this.player_, 'durationchange', this.update);
  10908. if (this.player_.liveTracker) {
  10909. this.on(this.player_.liveTracker, 'liveedgechange', this.update);
  10910. } // when playing, let's ensure we smoothly update the play progress bar
  10911. // via an interval
  10912. this.updateInterval = null;
  10913. this.on(this.player_, ['playing'], this.enableInterval_);
  10914. this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
  10915. // also, this causes the CPU to spike and eventually crash the page on IE11.
  10916. if ('hidden' in document && 'visibilityState' in document) {
  10917. this.on(document, 'visibilitychange', this.toggleVisibility_);
  10918. }
  10919. };
  10920. _proto.toggleVisibility_ = function toggleVisibility_(e) {
  10921. if (document.hidden) {
  10922. this.disableInterval_(e);
  10923. } else {
  10924. this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
  10925. this.requestAnimationFrame(this.update);
  10926. }
  10927. };
  10928. _proto.enableInterval_ = function enableInterval_() {
  10929. var _this2 = this;
  10930. this.clearInterval(this.updateInterval);
  10931. this.updateInterval = this.setInterval(function () {
  10932. _this2.requestAnimationFrame(_this2.update);
  10933. }, UPDATE_REFRESH_INTERVAL);
  10934. };
  10935. _proto.disableInterval_ = function disableInterval_(e) {
  10936. if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') {
  10937. return;
  10938. }
  10939. this.clearInterval(this.updateInterval);
  10940. }
  10941. /**
  10942. * Create the `Component`'s DOM element
  10943. *
  10944. * @return {Element}
  10945. * The element that was created.
  10946. */
  10947. ;
  10948. _proto.createEl = function createEl$$1() {
  10949. return _Slider.prototype.createEl.call(this, 'div', {
  10950. className: 'vjs-progress-holder'
  10951. }, {
  10952. 'aria-label': this.localize('Progress Bar')
  10953. });
  10954. }
  10955. /**
  10956. * This function updates the play progress bar and accessibility
  10957. * attributes to whatever is passed in.
  10958. *
  10959. * @param {number} currentTime
  10960. * The currentTime value that should be used for accessibility
  10961. *
  10962. * @param {number} percent
  10963. * The percentage as a decimal that the bar should be filled from 0-1.
  10964. *
  10965. * @private
  10966. */
  10967. ;
  10968. _proto.update_ = function update_(currentTime, percent) {
  10969. var liveTracker = this.player_.liveTracker;
  10970. var duration = this.player_.duration();
  10971. if (liveTracker && liveTracker.isLive()) {
  10972. duration = this.player_.liveTracker.liveCurrentTime();
  10973. } // machine readable value of progress bar (percentage complete)
  10974. this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete)
  10975. 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`.
  10976. if (this.bar) {
  10977. this.bar.update(getBoundingClientRect(this.el_), percent);
  10978. }
  10979. }
  10980. /**
  10981. * Update the seek bar's UI.
  10982. *
  10983. * @param {EventTarget~Event} [event]
  10984. * The `timeupdate` or `ended` event that caused this to run.
  10985. *
  10986. * @listens Player#timeupdate
  10987. *
  10988. * @return {number}
  10989. * The current percent at a number from 0-1
  10990. */
  10991. ;
  10992. _proto.update = function update(event) {
  10993. // if the offsetParent is null, then this element is hidden, in which case
  10994. // we don't need to update it.
  10995. if (this.el().offsetParent === null) {
  10996. return;
  10997. }
  10998. var percent = _Slider.prototype.update.call(this);
  10999. this.update_(this.getCurrentTime_(), percent);
  11000. return percent;
  11001. }
  11002. /**
  11003. * Get the value of current time but allows for smooth scrubbing,
  11004. * when player can't keep up.
  11005. *
  11006. * @return {number}
  11007. * The current time value to display
  11008. *
  11009. * @private
  11010. */
  11011. ;
  11012. _proto.getCurrentTime_ = function getCurrentTime_() {
  11013. return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  11014. }
  11015. /**
  11016. * We want the seek bar to be full on ended
  11017. * no matter what the actual internal values are. so we force it.
  11018. *
  11019. * @param {EventTarget~Event} [event]
  11020. * The `timeupdate` or `ended` event that caused this to run.
  11021. *
  11022. * @listens Player#ended
  11023. */
  11024. ;
  11025. _proto.handleEnded = function handleEnded(event) {
  11026. this.update_(this.player_.duration(), 1);
  11027. }
  11028. /**
  11029. * Get the percentage of media played so far.
  11030. *
  11031. * @return {number}
  11032. * The percentage of media played so far (0 to 1).
  11033. */
  11034. ;
  11035. _proto.getPercent = function getPercent() {
  11036. var currentTime = this.getCurrentTime_();
  11037. var percent;
  11038. var liveTracker = this.player_.liveTracker;
  11039. if (liveTracker && liveTracker.isLive()) {
  11040. percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
  11041. if (liveTracker.atLiveEdge()) {
  11042. percent = 1;
  11043. }
  11044. } else {
  11045. percent = currentTime / this.player_.duration();
  11046. }
  11047. return percent >= 1 ? 1 : percent || 0;
  11048. }
  11049. /**
  11050. * Handle mouse down on seek bar
  11051. *
  11052. * @param {EventTarget~Event} event
  11053. * The `mousedown` event that caused this to run.
  11054. *
  11055. * @listens mousedown
  11056. */
  11057. ;
  11058. _proto.handleMouseDown = function handleMouseDown(event) {
  11059. if (!isSingleLeftClick(event)) {
  11060. return;
  11061. } // Stop event propagation to prevent double fire in progress-control.js
  11062. event.stopPropagation();
  11063. this.player_.scrubbing(true);
  11064. this.videoWasPlaying = !this.player_.paused();
  11065. this.player_.pause();
  11066. _Slider.prototype.handleMouseDown.call(this, event);
  11067. }
  11068. /**
  11069. * Handle mouse move on seek bar
  11070. *
  11071. * @param {EventTarget~Event} event
  11072. * The `mousemove` event that caused this to run.
  11073. *
  11074. * @listens mousemove
  11075. */
  11076. ;
  11077. _proto.handleMouseMove = function handleMouseMove(event) {
  11078. if (!isSingleLeftClick(event)) {
  11079. return;
  11080. }
  11081. var newTime;
  11082. var distance = this.calculateDistance(event);
  11083. var liveTracker = this.player_.liveTracker;
  11084. if (!liveTracker || !liveTracker.isLive()) {
  11085. newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
  11086. if (newTime === this.player_.duration()) {
  11087. newTime = newTime - 0.1;
  11088. }
  11089. } else {
  11090. var seekableStart = liveTracker.seekableStart();
  11091. var seekableEnd = liveTracker.liveCurrentTime();
  11092. newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
  11093. if (newTime >= seekableEnd) {
  11094. newTime = seekableEnd;
  11095. } // Compensate for precision differences so that currentTime is not less
  11096. // than seekable start
  11097. if (newTime <= seekableStart) {
  11098. newTime = seekableStart + 0.1;
  11099. } // On android seekableEnd can be Infinity sometimes,
  11100. // this will cause newTime to be Infinity, which is
  11101. // not a valid currentTime.
  11102. if (newTime === Infinity) {
  11103. return;
  11104. }
  11105. } // Set new time (tell player to seek to new time)
  11106. this.player_.currentTime(newTime);
  11107. };
  11108. _proto.enable = function enable() {
  11109. _Slider.prototype.enable.call(this);
  11110. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  11111. if (!mouseTimeDisplay) {
  11112. return;
  11113. }
  11114. mouseTimeDisplay.show();
  11115. };
  11116. _proto.disable = function disable() {
  11117. _Slider.prototype.disable.call(this);
  11118. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  11119. if (!mouseTimeDisplay) {
  11120. return;
  11121. }
  11122. mouseTimeDisplay.hide();
  11123. }
  11124. /**
  11125. * Handle mouse up on seek bar
  11126. *
  11127. * @param {EventTarget~Event} event
  11128. * The `mouseup` event that caused this to run.
  11129. *
  11130. * @listens mouseup
  11131. */
  11132. ;
  11133. _proto.handleMouseUp = function handleMouseUp(event) {
  11134. _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
  11135. if (event) {
  11136. event.stopPropagation();
  11137. }
  11138. this.player_.scrubbing(false);
  11139. /**
  11140. * Trigger timeupdate because we're done seeking and the time has changed.
  11141. * This is particularly useful for if the player is paused to time the time displays.
  11142. *
  11143. * @event Tech#timeupdate
  11144. * @type {EventTarget~Event}
  11145. */
  11146. this.player_.trigger({
  11147. type: 'timeupdate',
  11148. target: this,
  11149. manuallyTriggered: true
  11150. });
  11151. if (this.videoWasPlaying) {
  11152. silencePromise(this.player_.play());
  11153. }
  11154. }
  11155. /**
  11156. * Move more quickly fast forward for keyboard-only users
  11157. */
  11158. ;
  11159. _proto.stepForward = function stepForward() {
  11160. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
  11161. }
  11162. /**
  11163. * Move more quickly rewind for keyboard-only users
  11164. */
  11165. ;
  11166. _proto.stepBack = function stepBack() {
  11167. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
  11168. }
  11169. /**
  11170. * Toggles the playback state of the player
  11171. * This gets called when enter or space is used on the seekbar
  11172. *
  11173. * @param {EventTarget~Event} event
  11174. * The `keydown` event that caused this function to be called
  11175. *
  11176. */
  11177. ;
  11178. _proto.handleAction = function handleAction(event) {
  11179. if (this.player_.paused()) {
  11180. this.player_.play();
  11181. } else {
  11182. this.player_.pause();
  11183. }
  11184. }
  11185. /**
  11186. * Called when this SeekBar has focus and a key gets pressed down.
  11187. * Supports the following keys:
  11188. *
  11189. * Space or Enter key fire a click event
  11190. * Home key moves to start of the timeline
  11191. * End key moves to end of the timeline
  11192. * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
  11193. * PageDown key moves back a larger step than ArrowDown
  11194. * PageUp key moves forward a large step
  11195. *
  11196. * @param {EventTarget~Event} event
  11197. * The `keydown` event that caused this function to be called.
  11198. *
  11199. * @listens keydown
  11200. */
  11201. ;
  11202. _proto.handleKeyPress = function handleKeyPress(event) {
  11203. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  11204. event.preventDefault();
  11205. this.handleAction(event);
  11206. } else if (keycode.isEventKey(event, 'Home')) {
  11207. event.preventDefault();
  11208. this.player_.currentTime(0);
  11209. } else if (keycode.isEventKey(event, 'End')) {
  11210. event.preventDefault();
  11211. this.player_.currentTime(this.player_.duration());
  11212. } else if (/^[0-9]$/.test(keycode(event))) {
  11213. event.preventDefault();
  11214. var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
  11215. this.player_.currentTime(this.player_.duration() * gotoFraction);
  11216. } else if (keycode.isEventKey(event, 'PgDn')) {
  11217. event.preventDefault();
  11218. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  11219. } else if (keycode.isEventKey(event, 'PgUp')) {
  11220. event.preventDefault();
  11221. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  11222. } else {
  11223. // Pass keypress handling up for unsupported keys
  11224. _Slider.prototype.handleKeyPress.call(this, event);
  11225. }
  11226. };
  11227. return SeekBar;
  11228. }(Slider);
  11229. /**
  11230. * Default options for the `SeekBar`
  11231. *
  11232. * @type {Object}
  11233. * @private
  11234. */
  11235. SeekBar.prototype.options_ = {
  11236. children: ['loadProgressBar', 'playProgressBar'],
  11237. barName: 'playProgressBar'
  11238. }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
  11239. if (!IS_IOS && !IS_ANDROID) {
  11240. SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
  11241. }
  11242. Component.registerComponent('SeekBar', SeekBar);
  11243. /**
  11244. * The Progress Control component contains the seek bar, load progress,
  11245. * and play progress.
  11246. *
  11247. * @extends Component
  11248. */
  11249. var ProgressControl =
  11250. /*#__PURE__*/
  11251. function (_Component) {
  11252. _inheritsLoose(ProgressControl, _Component);
  11253. /**
  11254. * Creates an instance of this class.
  11255. *
  11256. * @param {Player} player
  11257. * The `Player` that this class should be attached to.
  11258. *
  11259. * @param {Object} [options]
  11260. * The key/value store of player options.
  11261. */
  11262. function ProgressControl(player, options) {
  11263. var _this;
  11264. _this = _Component.call(this, player, options) || this;
  11265. _this.handleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
  11266. _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseSeek), 25);
  11267. _this.enable();
  11268. return _this;
  11269. }
  11270. /**
  11271. * Create the `Component`'s DOM element
  11272. *
  11273. * @return {Element}
  11274. * The element that was created.
  11275. */
  11276. var _proto = ProgressControl.prototype;
  11277. _proto.createEl = function createEl$$1() {
  11278. return _Component.prototype.createEl.call(this, 'div', {
  11279. className: 'vjs-progress-control vjs-control'
  11280. });
  11281. }
  11282. /**
  11283. * When the mouse moves over the `ProgressControl`, the pointer position
  11284. * gets passed down to the `MouseTimeDisplay` component.
  11285. *
  11286. * @param {EventTarget~Event} event
  11287. * The `mousemove` event that caused this function to run.
  11288. *
  11289. * @listen mousemove
  11290. */
  11291. ;
  11292. _proto.handleMouseMove = function handleMouseMove(event) {
  11293. var seekBar = this.getChild('seekBar');
  11294. if (seekBar) {
  11295. var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
  11296. var seekBarEl = seekBar.el();
  11297. var seekBarRect = getBoundingClientRect(seekBarEl);
  11298. var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
  11299. // that it's possible to trigger this behavior outside the boundaries of
  11300. // the `SeekBar`. This ensures we stay within it at all times.
  11301. if (seekBarPoint > 1) {
  11302. seekBarPoint = 1;
  11303. } else if (seekBarPoint < 0) {
  11304. seekBarPoint = 0;
  11305. }
  11306. if (mouseTimeDisplay) {
  11307. mouseTimeDisplay.update(seekBarRect, seekBarPoint);
  11308. }
  11309. }
  11310. }
  11311. /**
  11312. * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
  11313. *
  11314. * @method ProgressControl#throttledHandleMouseSeek
  11315. * @param {EventTarget~Event} event
  11316. * The `mousemove` event that caused this function to run.
  11317. *
  11318. * @listen mousemove
  11319. * @listen touchmove
  11320. */
  11321. /**
  11322. * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
  11323. *
  11324. * @param {EventTarget~Event} event
  11325. * `mousedown` or `touchstart` event that triggered this function
  11326. *
  11327. * @listens mousemove
  11328. * @listens touchmove
  11329. */
  11330. ;
  11331. _proto.handleMouseSeek = function handleMouseSeek(event) {
  11332. var seekBar = this.getChild('seekBar');
  11333. if (seekBar) {
  11334. seekBar.handleMouseMove(event);
  11335. }
  11336. }
  11337. /**
  11338. * Are controls are currently enabled for this progress control.
  11339. *
  11340. * @return {boolean}
  11341. * true if controls are enabled, false otherwise
  11342. */
  11343. ;
  11344. _proto.enabled = function enabled() {
  11345. return this.enabled_;
  11346. }
  11347. /**
  11348. * Disable all controls on the progress control and its children
  11349. */
  11350. ;
  11351. _proto.disable = function disable() {
  11352. this.children().forEach(function (child) {
  11353. return child.disable && child.disable();
  11354. });
  11355. if (!this.enabled()) {
  11356. return;
  11357. }
  11358. this.off(['mousedown', 'touchstart'], this.handleMouseDown);
  11359. this.off(this.el_, 'mousemove', this.handleMouseMove);
  11360. this.handleMouseUp();
  11361. this.addClass('disabled');
  11362. this.enabled_ = false;
  11363. }
  11364. /**
  11365. * Enable all controls on the progress control and its children
  11366. */
  11367. ;
  11368. _proto.enable = function enable() {
  11369. this.children().forEach(function (child) {
  11370. return child.enable && child.enable();
  11371. });
  11372. if (this.enabled()) {
  11373. return;
  11374. }
  11375. this.on(['mousedown', 'touchstart'], this.handleMouseDown);
  11376. this.on(this.el_, 'mousemove', this.handleMouseMove);
  11377. this.removeClass('disabled');
  11378. this.enabled_ = true;
  11379. }
  11380. /**
  11381. * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
  11382. *
  11383. * @param {EventTarget~Event} event
  11384. * `mousedown` or `touchstart` event that triggered this function
  11385. *
  11386. * @listens mousedown
  11387. * @listens touchstart
  11388. */
  11389. ;
  11390. _proto.handleMouseDown = function handleMouseDown(event) {
  11391. var doc = this.el_.ownerDocument;
  11392. var seekBar = this.getChild('seekBar');
  11393. if (seekBar) {
  11394. seekBar.handleMouseDown(event);
  11395. }
  11396. this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
  11397. this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
  11398. this.on(doc, 'mouseup', this.handleMouseUp);
  11399. this.on(doc, 'touchend', this.handleMouseUp);
  11400. }
  11401. /**
  11402. * Handle `mouseup` or `touchend` events on the `ProgressControl`.
  11403. *
  11404. * @param {EventTarget~Event} event
  11405. * `mouseup` or `touchend` event that triggered this function.
  11406. *
  11407. * @listens touchend
  11408. * @listens mouseup
  11409. */
  11410. ;
  11411. _proto.handleMouseUp = function handleMouseUp(event) {
  11412. var doc = this.el_.ownerDocument;
  11413. var seekBar = this.getChild('seekBar');
  11414. if (seekBar) {
  11415. seekBar.handleMouseUp(event);
  11416. }
  11417. this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
  11418. this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
  11419. this.off(doc, 'mouseup', this.handleMouseUp);
  11420. this.off(doc, 'touchend', this.handleMouseUp);
  11421. };
  11422. return ProgressControl;
  11423. }(Component);
  11424. /**
  11425. * Default options for `ProgressControl`
  11426. *
  11427. * @type {Object}
  11428. * @private
  11429. */
  11430. ProgressControl.prototype.options_ = {
  11431. children: ['seekBar']
  11432. };
  11433. Component.registerComponent('ProgressControl', ProgressControl);
  11434. /**
  11435. * Toggle fullscreen video
  11436. *
  11437. * @extends Button
  11438. */
  11439. var FullscreenToggle =
  11440. /*#__PURE__*/
  11441. function (_Button) {
  11442. _inheritsLoose(FullscreenToggle, _Button);
  11443. /**
  11444. * Creates an instance of this class.
  11445. *
  11446. * @param {Player} player
  11447. * The `Player` that this class should be attached to.
  11448. *
  11449. * @param {Object} [options]
  11450. * The key/value store of player options.
  11451. */
  11452. function FullscreenToggle(player, options) {
  11453. var _this;
  11454. _this = _Button.call(this, player, options) || this;
  11455. _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
  11456. if (document[FullscreenApi.fullscreenEnabled] === false) {
  11457. _this.disable();
  11458. }
  11459. return _this;
  11460. }
  11461. /**
  11462. * Builds the default DOM `className`.
  11463. *
  11464. * @return {string}
  11465. * The DOM `className` for this object.
  11466. */
  11467. var _proto = FullscreenToggle.prototype;
  11468. _proto.buildCSSClass = function buildCSSClass() {
  11469. return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
  11470. }
  11471. /**
  11472. * Handles fullscreenchange on the player and change control text accordingly.
  11473. *
  11474. * @param {EventTarget~Event} [event]
  11475. * The {@link Player#fullscreenchange} event that caused this function to be
  11476. * called.
  11477. *
  11478. * @listens Player#fullscreenchange
  11479. */
  11480. ;
  11481. _proto.handleFullscreenChange = function handleFullscreenChange(event) {
  11482. if (this.player_.isFullscreen()) {
  11483. this.controlText('Non-Fullscreen');
  11484. } else {
  11485. this.controlText('Fullscreen');
  11486. }
  11487. }
  11488. /**
  11489. * This gets called when an `FullscreenToggle` is "clicked". See
  11490. * {@link ClickableComponent} for more detailed information on what a click can be.
  11491. *
  11492. * @param {EventTarget~Event} [event]
  11493. * The `keydown`, `tap`, or `click` event that caused this function to be
  11494. * called.
  11495. *
  11496. * @listens tap
  11497. * @listens click
  11498. */
  11499. ;
  11500. _proto.handleClick = function handleClick(event) {
  11501. if (!this.player_.isFullscreen()) {
  11502. this.player_.requestFullscreen();
  11503. } else {
  11504. this.player_.exitFullscreen();
  11505. }
  11506. };
  11507. return FullscreenToggle;
  11508. }(Button);
  11509. /**
  11510. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  11511. *
  11512. * @type {string}
  11513. * @private
  11514. */
  11515. FullscreenToggle.prototype.controlText_ = 'Fullscreen';
  11516. Component.registerComponent('FullscreenToggle', FullscreenToggle);
  11517. /**
  11518. * Check if volume control is supported and if it isn't hide the
  11519. * `Component` that was passed using the `vjs-hidden` class.
  11520. *
  11521. * @param {Component} self
  11522. * The component that should be hidden if volume is unsupported
  11523. *
  11524. * @param {Player} player
  11525. * A reference to the player
  11526. *
  11527. * @private
  11528. */
  11529. var checkVolumeSupport = function checkVolumeSupport(self, player) {
  11530. // hide volume controls when they're not supported by the current tech
  11531. if (player.tech_ && !player.tech_.featuresVolumeControl) {
  11532. self.addClass('vjs-hidden');
  11533. }
  11534. self.on(player, 'loadstart', function () {
  11535. if (!player.tech_.featuresVolumeControl) {
  11536. self.addClass('vjs-hidden');
  11537. } else {
  11538. self.removeClass('vjs-hidden');
  11539. }
  11540. });
  11541. };
  11542. /**
  11543. * Shows volume level
  11544. *
  11545. * @extends Component
  11546. */
  11547. var VolumeLevel =
  11548. /*#__PURE__*/
  11549. function (_Component) {
  11550. _inheritsLoose(VolumeLevel, _Component);
  11551. function VolumeLevel() {
  11552. return _Component.apply(this, arguments) || this;
  11553. }
  11554. var _proto = VolumeLevel.prototype;
  11555. /**
  11556. * Create the `Component`'s DOM element
  11557. *
  11558. * @return {Element}
  11559. * The element that was created.
  11560. */
  11561. _proto.createEl = function createEl() {
  11562. return _Component.prototype.createEl.call(this, 'div', {
  11563. className: 'vjs-volume-level',
  11564. innerHTML: '<span class="vjs-control-text"></span>'
  11565. });
  11566. };
  11567. return VolumeLevel;
  11568. }(Component);
  11569. Component.registerComponent('VolumeLevel', VolumeLevel);
  11570. /**
  11571. * The bar that contains the volume level and can be clicked on to adjust the level
  11572. *
  11573. * @extends Slider
  11574. */
  11575. var VolumeBar =
  11576. /*#__PURE__*/
  11577. function (_Slider) {
  11578. _inheritsLoose(VolumeBar, _Slider);
  11579. /**
  11580. * Creates an instance of this class.
  11581. *
  11582. * @param {Player} player
  11583. * The `Player` that this class should be attached to.
  11584. *
  11585. * @param {Object} [options]
  11586. * The key/value store of player options.
  11587. */
  11588. function VolumeBar(player, options) {
  11589. var _this;
  11590. _this = _Slider.call(this, player, options) || this;
  11591. _this.on('slideractive', _this.updateLastVolume_);
  11592. _this.on(player, 'volumechange', _this.updateARIAAttributes);
  11593. player.ready(function () {
  11594. return _this.updateARIAAttributes();
  11595. });
  11596. return _this;
  11597. }
  11598. /**
  11599. * Create the `Component`'s DOM element
  11600. *
  11601. * @return {Element}
  11602. * The element that was created.
  11603. */
  11604. var _proto = VolumeBar.prototype;
  11605. _proto.createEl = function createEl$$1() {
  11606. return _Slider.prototype.createEl.call(this, 'div', {
  11607. className: 'vjs-volume-bar vjs-slider-bar'
  11608. }, {
  11609. 'aria-label': this.localize('Volume Level'),
  11610. 'aria-live': 'polite'
  11611. });
  11612. }
  11613. /**
  11614. * Handle mouse down on volume bar
  11615. *
  11616. * @param {EventTarget~Event} event
  11617. * The `mousedown` event that caused this to run.
  11618. *
  11619. * @listens mousedown
  11620. */
  11621. ;
  11622. _proto.handleMouseDown = function handleMouseDown(event) {
  11623. if (!isSingleLeftClick(event)) {
  11624. return;
  11625. }
  11626. _Slider.prototype.handleMouseDown.call(this, event);
  11627. }
  11628. /**
  11629. * Handle movement events on the {@link VolumeMenuButton}.
  11630. *
  11631. * @param {EventTarget~Event} event
  11632. * The event that caused this function to run.
  11633. *
  11634. * @listens mousemove
  11635. */
  11636. ;
  11637. _proto.handleMouseMove = function handleMouseMove(event) {
  11638. if (!isSingleLeftClick(event)) {
  11639. return;
  11640. }
  11641. this.checkMuted();
  11642. this.player_.volume(this.calculateDistance(event));
  11643. }
  11644. /**
  11645. * If the player is muted unmute it.
  11646. */
  11647. ;
  11648. _proto.checkMuted = function checkMuted() {
  11649. if (this.player_.muted()) {
  11650. this.player_.muted(false);
  11651. }
  11652. }
  11653. /**
  11654. * Get percent of volume level
  11655. *
  11656. * @return {number}
  11657. * Volume level percent as a decimal number.
  11658. */
  11659. ;
  11660. _proto.getPercent = function getPercent() {
  11661. if (this.player_.muted()) {
  11662. return 0;
  11663. }
  11664. return this.player_.volume();
  11665. }
  11666. /**
  11667. * Increase volume level for keyboard users
  11668. */
  11669. ;
  11670. _proto.stepForward = function stepForward() {
  11671. this.checkMuted();
  11672. this.player_.volume(this.player_.volume() + 0.1);
  11673. }
  11674. /**
  11675. * Decrease volume level for keyboard users
  11676. */
  11677. ;
  11678. _proto.stepBack = function stepBack() {
  11679. this.checkMuted();
  11680. this.player_.volume(this.player_.volume() - 0.1);
  11681. }
  11682. /**
  11683. * Update ARIA accessibility attributes
  11684. *
  11685. * @param {EventTarget~Event} [event]
  11686. * The `volumechange` event that caused this function to run.
  11687. *
  11688. * @listens Player#volumechange
  11689. */
  11690. ;
  11691. _proto.updateARIAAttributes = function updateARIAAttributes(event) {
  11692. var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
  11693. this.el_.setAttribute('aria-valuenow', ariaValue);
  11694. this.el_.setAttribute('aria-valuetext', ariaValue + '%');
  11695. }
  11696. /**
  11697. * Returns the current value of the player volume as a percentage
  11698. *
  11699. * @private
  11700. */
  11701. ;
  11702. _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
  11703. return Math.round(this.player_.volume() * 100);
  11704. }
  11705. /**
  11706. * When user starts dragging the VolumeBar, store the volume and listen for
  11707. * the end of the drag. When the drag ends, if the volume was set to zero,
  11708. * set lastVolume to the stored volume.
  11709. *
  11710. * @listens slideractive
  11711. * @private
  11712. */
  11713. ;
  11714. _proto.updateLastVolume_ = function updateLastVolume_() {
  11715. var _this2 = this;
  11716. var volumeBeforeDrag = this.player_.volume();
  11717. this.one('sliderinactive', function () {
  11718. if (_this2.player_.volume() === 0) {
  11719. _this2.player_.lastVolume_(volumeBeforeDrag);
  11720. }
  11721. });
  11722. };
  11723. return VolumeBar;
  11724. }(Slider);
  11725. /**
  11726. * Default options for the `VolumeBar`
  11727. *
  11728. * @type {Object}
  11729. * @private
  11730. */
  11731. VolumeBar.prototype.options_ = {
  11732. children: ['volumeLevel'],
  11733. barName: 'volumeLevel'
  11734. };
  11735. /**
  11736. * Call the update event for this Slider when this event happens on the player.
  11737. *
  11738. * @type {string}
  11739. */
  11740. VolumeBar.prototype.playerEvent = 'volumechange';
  11741. Component.registerComponent('VolumeBar', VolumeBar);
  11742. /**
  11743. * The component for controlling the volume level
  11744. *
  11745. * @extends Component
  11746. */
  11747. var VolumeControl =
  11748. /*#__PURE__*/
  11749. function (_Component) {
  11750. _inheritsLoose(VolumeControl, _Component);
  11751. /**
  11752. * Creates an instance of this class.
  11753. *
  11754. * @param {Player} player
  11755. * The `Player` that this class should be attached to.
  11756. *
  11757. * @param {Object} [options={}]
  11758. * The key/value store of player options.
  11759. */
  11760. function VolumeControl(player, options) {
  11761. var _this;
  11762. if (options === void 0) {
  11763. options = {};
  11764. }
  11765. options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
  11766. // the VolumeBar is turned on.
  11767. if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
  11768. options.volumeBar = options.volumeBar || {};
  11769. options.volumeBar.vertical = options.vertical;
  11770. }
  11771. _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
  11772. checkVolumeSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
  11773. _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
  11774. _this.on('mousedown', _this.handleMouseDown);
  11775. _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and
  11776. // is dragging) or in focus we do not want to hide the VolumeBar
  11777. _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
  11778. _this.volumeBar.addClass('vjs-slider-active');
  11779. _this.addClass('vjs-slider-active');
  11780. _this.trigger('slideractive');
  11781. });
  11782. _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
  11783. _this.volumeBar.removeClass('vjs-slider-active');
  11784. _this.removeClass('vjs-slider-active');
  11785. _this.trigger('sliderinactive');
  11786. });
  11787. return _this;
  11788. }
  11789. /**
  11790. * Create the `Component`'s DOM element
  11791. *
  11792. * @return {Element}
  11793. * The element that was created.
  11794. */
  11795. var _proto = VolumeControl.prototype;
  11796. _proto.createEl = function createEl() {
  11797. var orientationClass = 'vjs-volume-horizontal';
  11798. if (this.options_.vertical) {
  11799. orientationClass = 'vjs-volume-vertical';
  11800. }
  11801. return _Component.prototype.createEl.call(this, 'div', {
  11802. className: "vjs-volume-control vjs-control " + orientationClass
  11803. });
  11804. }
  11805. /**
  11806. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  11807. *
  11808. * @param {EventTarget~Event} event
  11809. * `mousedown` or `touchstart` event that triggered this function
  11810. *
  11811. * @listens mousedown
  11812. * @listens touchstart
  11813. */
  11814. ;
  11815. _proto.handleMouseDown = function handleMouseDown(event) {
  11816. var doc = this.el_.ownerDocument;
  11817. this.on(doc, 'mousemove', this.throttledHandleMouseMove);
  11818. this.on(doc, 'touchmove', this.throttledHandleMouseMove);
  11819. this.on(doc, 'mouseup', this.handleMouseUp);
  11820. this.on(doc, 'touchend', this.handleMouseUp);
  11821. }
  11822. /**
  11823. * Handle `mouseup` or `touchend` events on the `VolumeControl`.
  11824. *
  11825. * @param {EventTarget~Event} event
  11826. * `mouseup` or `touchend` event that triggered this function.
  11827. *
  11828. * @listens touchend
  11829. * @listens mouseup
  11830. */
  11831. ;
  11832. _proto.handleMouseUp = function handleMouseUp(event) {
  11833. var doc = this.el_.ownerDocument;
  11834. this.off(doc, 'mousemove', this.throttledHandleMouseMove);
  11835. this.off(doc, 'touchmove', this.throttledHandleMouseMove);
  11836. this.off(doc, 'mouseup', this.handleMouseUp);
  11837. this.off(doc, 'touchend', this.handleMouseUp);
  11838. }
  11839. /**
  11840. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  11841. *
  11842. * @param {EventTarget~Event} event
  11843. * `mousedown` or `touchstart` event that triggered this function
  11844. *
  11845. * @listens mousedown
  11846. * @listens touchstart
  11847. */
  11848. ;
  11849. _proto.handleMouseMove = function handleMouseMove(event) {
  11850. this.volumeBar.handleMouseMove(event);
  11851. };
  11852. return VolumeControl;
  11853. }(Component);
  11854. /**
  11855. * Default options for the `VolumeControl`
  11856. *
  11857. * @type {Object}
  11858. * @private
  11859. */
  11860. VolumeControl.prototype.options_ = {
  11861. children: ['volumeBar']
  11862. };
  11863. Component.registerComponent('VolumeControl', VolumeControl);
  11864. /**
  11865. * Check if muting volume is supported and if it isn't hide the mute toggle
  11866. * button.
  11867. *
  11868. * @param {Component} self
  11869. * A reference to the mute toggle button
  11870. *
  11871. * @param {Player} player
  11872. * A reference to the player
  11873. *
  11874. * @private
  11875. */
  11876. var checkMuteSupport = function checkMuteSupport(self, player) {
  11877. // hide mute toggle button if it's not supported by the current tech
  11878. if (player.tech_ && !player.tech_.featuresMuteControl) {
  11879. self.addClass('vjs-hidden');
  11880. }
  11881. self.on(player, 'loadstart', function () {
  11882. if (!player.tech_.featuresMuteControl) {
  11883. self.addClass('vjs-hidden');
  11884. } else {
  11885. self.removeClass('vjs-hidden');
  11886. }
  11887. });
  11888. };
  11889. /**
  11890. * A button component for muting the audio.
  11891. *
  11892. * @extends Button
  11893. */
  11894. var MuteToggle =
  11895. /*#__PURE__*/
  11896. function (_Button) {
  11897. _inheritsLoose(MuteToggle, _Button);
  11898. /**
  11899. * Creates an instance of this class.
  11900. *
  11901. * @param {Player} player
  11902. * The `Player` that this class should be attached to.
  11903. *
  11904. * @param {Object} [options]
  11905. * The key/value store of player options.
  11906. */
  11907. function MuteToggle(player, options) {
  11908. var _this;
  11909. _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
  11910. checkMuteSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
  11911. _this.on(player, ['loadstart', 'volumechange'], _this.update);
  11912. return _this;
  11913. }
  11914. /**
  11915. * Builds the default DOM `className`.
  11916. *
  11917. * @return {string}
  11918. * The DOM `className` for this object.
  11919. */
  11920. var _proto = MuteToggle.prototype;
  11921. _proto.buildCSSClass = function buildCSSClass() {
  11922. return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
  11923. }
  11924. /**
  11925. * This gets called when an `MuteToggle` is "clicked". See
  11926. * {@link ClickableComponent} for more detailed information on what a click can be.
  11927. *
  11928. * @param {EventTarget~Event} [event]
  11929. * The `keydown`, `tap`, or `click` event that caused this function to be
  11930. * called.
  11931. *
  11932. * @listens tap
  11933. * @listens click
  11934. */
  11935. ;
  11936. _proto.handleClick = function handleClick(event) {
  11937. var vol = this.player_.volume();
  11938. var lastVolume = this.player_.lastVolume_();
  11939. if (vol === 0) {
  11940. var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
  11941. this.player_.volume(volumeToSet);
  11942. this.player_.muted(false);
  11943. } else {
  11944. this.player_.muted(this.player_.muted() ? false : true);
  11945. }
  11946. }
  11947. /**
  11948. * Update the `MuteToggle` button based on the state of `volume` and `muted`
  11949. * on the player.
  11950. *
  11951. * @param {EventTarget~Event} [event]
  11952. * The {@link Player#loadstart} event if this function was called
  11953. * through an event.
  11954. *
  11955. * @listens Player#loadstart
  11956. * @listens Player#volumechange
  11957. */
  11958. ;
  11959. _proto.update = function update(event) {
  11960. this.updateIcon_();
  11961. this.updateControlText_();
  11962. }
  11963. /**
  11964. * Update the appearance of the `MuteToggle` icon.
  11965. *
  11966. * Possible states (given `level` variable below):
  11967. * - 0: crossed out
  11968. * - 1: zero bars of volume
  11969. * - 2: one bar of volume
  11970. * - 3: two bars of volume
  11971. *
  11972. * @private
  11973. */
  11974. ;
  11975. _proto.updateIcon_ = function updateIcon_() {
  11976. var vol = this.player_.volume();
  11977. var level = 3; // in iOS when a player is loaded with muted attribute
  11978. // and volume is changed with a native mute button
  11979. // we want to make sure muted state is updated
  11980. if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
  11981. this.player_.muted(this.player_.tech_.el_.muted);
  11982. }
  11983. if (vol === 0 || this.player_.muted()) {
  11984. level = 0;
  11985. } else if (vol < 0.33) {
  11986. level = 1;
  11987. } else if (vol < 0.67) {
  11988. level = 2;
  11989. } // TODO improve muted icon classes
  11990. for (var i = 0; i < 4; i++) {
  11991. removeClass(this.el_, "vjs-vol-" + i);
  11992. }
  11993. addClass(this.el_, "vjs-vol-" + level);
  11994. }
  11995. /**
  11996. * If `muted` has changed on the player, update the control text
  11997. * (`title` attribute on `vjs-mute-control` element and content of
  11998. * `vjs-control-text` element).
  11999. *
  12000. * @private
  12001. */
  12002. ;
  12003. _proto.updateControlText_ = function updateControlText_() {
  12004. var soundOff = this.player_.muted() || this.player_.volume() === 0;
  12005. var text = soundOff ? 'Unmute' : 'Mute';
  12006. if (this.controlText() !== text) {
  12007. this.controlText(text);
  12008. }
  12009. };
  12010. return MuteToggle;
  12011. }(Button);
  12012. /**
  12013. * The text that should display over the `MuteToggle`s controls. Added for localization.
  12014. *
  12015. * @type {string}
  12016. * @private
  12017. */
  12018. MuteToggle.prototype.controlText_ = 'Mute';
  12019. Component.registerComponent('MuteToggle', MuteToggle);
  12020. /**
  12021. * A Component to contain the MuteToggle and VolumeControl so that
  12022. * they can work together.
  12023. *
  12024. * @extends Component
  12025. */
  12026. var VolumePanel =
  12027. /*#__PURE__*/
  12028. function (_Component) {
  12029. _inheritsLoose(VolumePanel, _Component);
  12030. /**
  12031. * Creates an instance of this class.
  12032. *
  12033. * @param {Player} player
  12034. * The `Player` that this class should be attached to.
  12035. *
  12036. * @param {Object} [options={}]
  12037. * The key/value store of player options.
  12038. */
  12039. function VolumePanel(player, options) {
  12040. var _this;
  12041. if (options === void 0) {
  12042. options = {};
  12043. }
  12044. if (typeof options.inline !== 'undefined') {
  12045. options.inline = options.inline;
  12046. } else {
  12047. options.inline = true;
  12048. } // pass the inline option down to the VolumeControl as vertical if
  12049. // the VolumeControl is on.
  12050. if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
  12051. options.volumeControl = options.volumeControl || {};
  12052. options.volumeControl.vertical = !options.inline;
  12053. }
  12054. _this = _Component.call(this, player, options) || this;
  12055. _this.on(player, ['loadstart'], _this.volumePanelState_); // while the slider is active (the mouse has been pressed down and
  12056. // is dragging) we do not want to hide the VolumeBar
  12057. _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
  12058. _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
  12059. return _this;
  12060. }
  12061. /**
  12062. * Add vjs-slider-active class to the VolumePanel
  12063. *
  12064. * @listens VolumeControl#slideractive
  12065. * @private
  12066. */
  12067. var _proto = VolumePanel.prototype;
  12068. _proto.sliderActive_ = function sliderActive_() {
  12069. this.addClass('vjs-slider-active');
  12070. }
  12071. /**
  12072. * Removes vjs-slider-active class to the VolumePanel
  12073. *
  12074. * @listens VolumeControl#sliderinactive
  12075. * @private
  12076. */
  12077. ;
  12078. _proto.sliderInactive_ = function sliderInactive_() {
  12079. this.removeClass('vjs-slider-active');
  12080. }
  12081. /**
  12082. * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
  12083. * depending on MuteToggle and VolumeControl state
  12084. *
  12085. * @listens Player#loadstart
  12086. * @private
  12087. */
  12088. ;
  12089. _proto.volumePanelState_ = function volumePanelState_() {
  12090. // hide volume panel if neither volume control or mute toggle
  12091. // are displayed
  12092. if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
  12093. this.addClass('vjs-hidden');
  12094. } // if only mute toggle is visible we don't want
  12095. // volume panel expanding when hovered or active
  12096. if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
  12097. this.addClass('vjs-mute-toggle-only');
  12098. }
  12099. }
  12100. /**
  12101. * Create the `Component`'s DOM element
  12102. *
  12103. * @return {Element}
  12104. * The element that was created.
  12105. */
  12106. ;
  12107. _proto.createEl = function createEl() {
  12108. var orientationClass = 'vjs-volume-panel-horizontal';
  12109. if (!this.options_.inline) {
  12110. orientationClass = 'vjs-volume-panel-vertical';
  12111. }
  12112. return _Component.prototype.createEl.call(this, 'div', {
  12113. className: "vjs-volume-panel vjs-control " + orientationClass
  12114. });
  12115. };
  12116. return VolumePanel;
  12117. }(Component);
  12118. /**
  12119. * Default options for the `VolumeControl`
  12120. *
  12121. * @type {Object}
  12122. * @private
  12123. */
  12124. VolumePanel.prototype.options_ = {
  12125. children: ['muteToggle', 'volumeControl']
  12126. };
  12127. Component.registerComponent('VolumePanel', VolumePanel);
  12128. /**
  12129. * The Menu component is used to build popup menus, including subtitle and
  12130. * captions selection menus.
  12131. *
  12132. * @extends Component
  12133. */
  12134. var Menu =
  12135. /*#__PURE__*/
  12136. function (_Component) {
  12137. _inheritsLoose(Menu, _Component);
  12138. /**
  12139. * Create an instance of this class.
  12140. *
  12141. * @param {Player} player
  12142. * the player that this component should attach to
  12143. *
  12144. * @param {Object} [options]
  12145. * Object of option names and values
  12146. *
  12147. */
  12148. function Menu(player, options) {
  12149. var _this;
  12150. _this = _Component.call(this, player, options) || this;
  12151. if (options) {
  12152. _this.menuButton_ = options.menuButton;
  12153. }
  12154. _this.focusedChild_ = -1;
  12155. _this.on('keydown', _this.handleKeyPress); // All the menu item instances share the same blur handler provided by the menu container.
  12156. _this.boundHandleBlur_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleBlur);
  12157. _this.boundHandleTapClick_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTapClick);
  12158. return _this;
  12159. }
  12160. /**
  12161. * Add event listeners to the {@link MenuItem}.
  12162. *
  12163. * @param {Object} component
  12164. * The instance of the `MenuItem` to add listeners to.
  12165. *
  12166. */
  12167. var _proto = Menu.prototype;
  12168. _proto.addEventListenerForItem = function addEventListenerForItem(component) {
  12169. if (!(component instanceof Component)) {
  12170. return;
  12171. }
  12172. this.on(component, 'blur', this.boundHandleBlur_);
  12173. this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
  12174. }
  12175. /**
  12176. * Remove event listeners from the {@link MenuItem}.
  12177. *
  12178. * @param {Object} component
  12179. * The instance of the `MenuItem` to remove listeners.
  12180. *
  12181. */
  12182. ;
  12183. _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
  12184. if (!(component instanceof Component)) {
  12185. return;
  12186. }
  12187. this.off(component, 'blur', this.boundHandleBlur_);
  12188. this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
  12189. }
  12190. /**
  12191. * This method will be called indirectly when the component has been added
  12192. * before the component adds to the new menu instance by `addItem`.
  12193. * In this case, the original menu instance will remove the component
  12194. * by calling `removeChild`.
  12195. *
  12196. * @param {Object} component
  12197. * The instance of the `MenuItem`
  12198. */
  12199. ;
  12200. _proto.removeChild = function removeChild(component) {
  12201. if (typeof component === 'string') {
  12202. component = this.getChild(component);
  12203. }
  12204. this.removeEventListenerForItem(component);
  12205. _Component.prototype.removeChild.call(this, component);
  12206. }
  12207. /**
  12208. * Add a {@link MenuItem} to the menu.
  12209. *
  12210. * @param {Object|string} component
  12211. * The name or instance of the `MenuItem` to add.
  12212. *
  12213. */
  12214. ;
  12215. _proto.addItem = function addItem(component) {
  12216. var childComponent = this.addChild(component);
  12217. if (childComponent) {
  12218. this.addEventListenerForItem(childComponent);
  12219. }
  12220. }
  12221. /**
  12222. * Create the `Menu`s DOM element.
  12223. *
  12224. * @return {Element}
  12225. * the element that was created
  12226. */
  12227. ;
  12228. _proto.createEl = function createEl$$1() {
  12229. var contentElType = this.options_.contentElType || 'ul';
  12230. this.contentEl_ = createEl(contentElType, {
  12231. className: 'vjs-menu-content'
  12232. });
  12233. this.contentEl_.setAttribute('role', 'menu');
  12234. var el = _Component.prototype.createEl.call(this, 'div', {
  12235. append: this.contentEl_,
  12236. className: 'vjs-menu'
  12237. });
  12238. el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
  12239. // where a click on the parent is significant
  12240. on(el, 'click', function (event) {
  12241. event.preventDefault();
  12242. event.stopImmediatePropagation();
  12243. });
  12244. return el;
  12245. };
  12246. _proto.dispose = function dispose() {
  12247. this.contentEl_ = null;
  12248. this.boundHandleBlur_ = null;
  12249. this.boundHandleTapClick_ = null;
  12250. _Component.prototype.dispose.call(this);
  12251. }
  12252. /**
  12253. * Called when a `MenuItem` loses focus.
  12254. *
  12255. * @param {EventTarget~Event} event
  12256. * The `blur` event that caused this function to be called.
  12257. *
  12258. * @listens blur
  12259. */
  12260. ;
  12261. _proto.handleBlur = function handleBlur(event) {
  12262. var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
  12263. if (!this.children().some(function (element) {
  12264. return element.el() === relatedTarget;
  12265. })) {
  12266. var btn = this.menuButton_;
  12267. if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
  12268. btn.unpressButton();
  12269. }
  12270. }
  12271. }
  12272. /**
  12273. * Called when a `MenuItem` gets clicked or tapped.
  12274. *
  12275. * @param {EventTarget~Event} event
  12276. * The `click` or `tap` event that caused this function to be called.
  12277. *
  12278. * @listens click,tap
  12279. */
  12280. ;
  12281. _proto.handleTapClick = function handleTapClick(event) {
  12282. // Unpress the associated MenuButton, and move focus back to it
  12283. if (this.menuButton_) {
  12284. this.menuButton_.unpressButton();
  12285. var childComponents = this.children();
  12286. if (!Array.isArray(childComponents)) {
  12287. return;
  12288. }
  12289. var foundComponent = childComponents.filter(function (component) {
  12290. return component.el() === event.target;
  12291. })[0];
  12292. if (!foundComponent) {
  12293. return;
  12294. } // don't focus menu button if item is a caption settings item
  12295. // because focus will move elsewhere
  12296. if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
  12297. this.menuButton_.focus();
  12298. }
  12299. }
  12300. }
  12301. /**
  12302. * Handle a `keydown` event on this menu. This listener is added in the constructor.
  12303. *
  12304. * @param {EventTarget~Event} event
  12305. * A `keydown` event that happened on the menu.
  12306. *
  12307. * @listens keydown
  12308. */
  12309. ;
  12310. _proto.handleKeyPress = function handleKeyPress(event) {
  12311. // Left and Down Arrows
  12312. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  12313. event.preventDefault();
  12314. this.stepForward(); // Up and Right Arrows
  12315. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  12316. event.preventDefault();
  12317. this.stepBack();
  12318. }
  12319. }
  12320. /**
  12321. * Move to next (lower) menu item for keyboard users.
  12322. */
  12323. ;
  12324. _proto.stepForward = function stepForward() {
  12325. var stepChild = 0;
  12326. if (this.focusedChild_ !== undefined) {
  12327. stepChild = this.focusedChild_ + 1;
  12328. }
  12329. this.focus(stepChild);
  12330. }
  12331. /**
  12332. * Move to previous (higher) menu item for keyboard users.
  12333. */
  12334. ;
  12335. _proto.stepBack = function stepBack() {
  12336. var stepChild = 0;
  12337. if (this.focusedChild_ !== undefined) {
  12338. stepChild = this.focusedChild_ - 1;
  12339. }
  12340. this.focus(stepChild);
  12341. }
  12342. /**
  12343. * Set focus on a {@link MenuItem} in the `Menu`.
  12344. *
  12345. * @param {Object|string} [item=0]
  12346. * Index of child item set focus on.
  12347. */
  12348. ;
  12349. _proto.focus = function focus(item) {
  12350. if (item === void 0) {
  12351. item = 0;
  12352. }
  12353. var children = this.children().slice();
  12354. var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
  12355. if (haveTitle) {
  12356. children.shift();
  12357. }
  12358. if (children.length > 0) {
  12359. if (item < 0) {
  12360. item = 0;
  12361. } else if (item >= children.length) {
  12362. item = children.length - 1;
  12363. }
  12364. this.focusedChild_ = item;
  12365. children[item].el_.focus();
  12366. }
  12367. };
  12368. return Menu;
  12369. }(Component);
  12370. Component.registerComponent('Menu', Menu);
  12371. /**
  12372. * A `MenuButton` class for any popup {@link Menu}.
  12373. *
  12374. * @extends Component
  12375. */
  12376. var MenuButton =
  12377. /*#__PURE__*/
  12378. function (_Component) {
  12379. _inheritsLoose(MenuButton, _Component);
  12380. /**
  12381. * Creates an instance of this class.
  12382. *
  12383. * @param {Player} player
  12384. * The `Player` that this class should be attached to.
  12385. *
  12386. * @param {Object} [options={}]
  12387. * The key/value store of player options.
  12388. */
  12389. function MenuButton(player, options) {
  12390. var _this;
  12391. if (options === void 0) {
  12392. options = {};
  12393. }
  12394. _this = _Component.call(this, player, options) || this;
  12395. _this.menuButton_ = new Button(player, options);
  12396. _this.menuButton_.controlText(_this.controlText_);
  12397. _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
  12398. var buttonClass = Button.prototype.buildCSSClass();
  12399. _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
  12400. _this.menuButton_.removeClass('vjs-control');
  12401. _this.addChild(_this.menuButton_);
  12402. _this.update();
  12403. _this.enabled_ = true;
  12404. _this.on(_this.menuButton_, 'tap', _this.handleClick);
  12405. _this.on(_this.menuButton_, 'click', _this.handleClick);
  12406. _this.on(_this.menuButton_, 'focus', _this.handleFocus);
  12407. _this.on(_this.menuButton_, 'blur', _this.handleBlur);
  12408. _this.on(_this.menuButton_, 'mouseenter', function () {
  12409. _this.menu.show();
  12410. });
  12411. _this.on('keydown', _this.handleSubmenuKeyPress);
  12412. return _this;
  12413. }
  12414. /**
  12415. * Update the menu based on the current state of its items.
  12416. */
  12417. var _proto = MenuButton.prototype;
  12418. _proto.update = function update() {
  12419. var menu = this.createMenu();
  12420. if (this.menu) {
  12421. this.menu.dispose();
  12422. this.removeChild(this.menu);
  12423. }
  12424. this.menu = menu;
  12425. this.addChild(menu);
  12426. /**
  12427. * Track the state of the menu button
  12428. *
  12429. * @type {Boolean}
  12430. * @private
  12431. */
  12432. this.buttonPressed_ = false;
  12433. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  12434. if (this.items && this.items.length <= this.hideThreshold_) {
  12435. this.hide();
  12436. } else {
  12437. this.show();
  12438. }
  12439. }
  12440. /**
  12441. * Create the menu and add all items to it.
  12442. *
  12443. * @return {Menu}
  12444. * The constructed menu
  12445. */
  12446. ;
  12447. _proto.createMenu = function createMenu() {
  12448. var menu = new Menu(this.player_, {
  12449. menuButton: this
  12450. });
  12451. /**
  12452. * Hide the menu if the number of items is less than or equal to this threshold. This defaults
  12453. * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
  12454. * it here because every time we run `createMenu` we need to reset the value.
  12455. *
  12456. * @protected
  12457. * @type {Number}
  12458. */
  12459. this.hideThreshold_ = 0; // Add a title list item to the top
  12460. if (this.options_.title) {
  12461. var titleEl = createEl('li', {
  12462. className: 'vjs-menu-title',
  12463. innerHTML: toTitleCase(this.options_.title),
  12464. tabIndex: -1
  12465. });
  12466. this.hideThreshold_ += 1;
  12467. var titleComponent = new Component(this.player_, {
  12468. el: titleEl
  12469. });
  12470. menu.addItem(titleComponent);
  12471. }
  12472. this.items = this.createItems();
  12473. if (this.items) {
  12474. // Add menu items to the menu
  12475. for (var i = 0; i < this.items.length; i++) {
  12476. menu.addItem(this.items[i]);
  12477. }
  12478. }
  12479. return menu;
  12480. }
  12481. /**
  12482. * Create the list of menu items. Specific to each subclass.
  12483. *
  12484. * @abstract
  12485. */
  12486. ;
  12487. _proto.createItems = function createItems() {}
  12488. /**
  12489. * Create the `MenuButtons`s DOM element.
  12490. *
  12491. * @return {Element}
  12492. * The element that gets created.
  12493. */
  12494. ;
  12495. _proto.createEl = function createEl$$1() {
  12496. return _Component.prototype.createEl.call(this, 'div', {
  12497. className: this.buildWrapperCSSClass()
  12498. }, {});
  12499. }
  12500. /**
  12501. * Allow sub components to stack CSS class names for the wrapper element
  12502. *
  12503. * @return {string}
  12504. * The constructed wrapper DOM `className`
  12505. */
  12506. ;
  12507. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  12508. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  12509. if (this.options_.inline === true) {
  12510. menuButtonClass += '-inline';
  12511. } else {
  12512. menuButtonClass += '-popup';
  12513. } // TODO: Fix the CSS so that this isn't necessary
  12514. var buttonClass = Button.prototype.buildCSSClass();
  12515. return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
  12516. }
  12517. /**
  12518. * Builds the default DOM `className`.
  12519. *
  12520. * @return {string}
  12521. * The DOM `className` for this object.
  12522. */
  12523. ;
  12524. _proto.buildCSSClass = function buildCSSClass() {
  12525. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  12526. if (this.options_.inline === true) {
  12527. menuButtonClass += '-inline';
  12528. } else {
  12529. menuButtonClass += '-popup';
  12530. }
  12531. return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
  12532. }
  12533. /**
  12534. * Get or set the localized control text that will be used for accessibility.
  12535. *
  12536. * > NOTE: This will come from the internal `menuButton_` element.
  12537. *
  12538. * @param {string} [text]
  12539. * Control text for element.
  12540. *
  12541. * @param {Element} [el=this.menuButton_.el()]
  12542. * Element to set the title on.
  12543. *
  12544. * @return {string}
  12545. * - The control text when getting
  12546. */
  12547. ;
  12548. _proto.controlText = function controlText(text, el) {
  12549. if (el === void 0) {
  12550. el = this.menuButton_.el();
  12551. }
  12552. return this.menuButton_.controlText(text, el);
  12553. }
  12554. /**
  12555. * Handle a click on a `MenuButton`.
  12556. * See {@link ClickableComponent#handleClick} for instances where this is called.
  12557. *
  12558. * @param {EventTarget~Event} event
  12559. * The `keydown`, `tap`, or `click` event that caused this function to be
  12560. * called.
  12561. *
  12562. * @listens tap
  12563. * @listens click
  12564. */
  12565. ;
  12566. _proto.handleClick = function handleClick(event) {
  12567. if (this.buttonPressed_) {
  12568. this.unpressButton();
  12569. } else {
  12570. this.pressButton();
  12571. }
  12572. }
  12573. /**
  12574. * Set the focus to the actual button, not to this element
  12575. */
  12576. ;
  12577. _proto.focus = function focus() {
  12578. this.menuButton_.focus();
  12579. }
  12580. /**
  12581. * Remove the focus from the actual button, not this element
  12582. */
  12583. ;
  12584. _proto.blur = function blur() {
  12585. this.menuButton_.blur();
  12586. }
  12587. /**
  12588. * This gets called when a `MenuButton` gains focus via a `focus` event.
  12589. * Turns on listening for `keydown` events. When they happen it
  12590. * calls `this.handleKeyPress`.
  12591. *
  12592. * @param {EventTarget~Event} event
  12593. * The `focus` event that caused this function to be called.
  12594. *
  12595. * @listens focus
  12596. */
  12597. ;
  12598. _proto.handleFocus = function handleFocus() {
  12599. on(document, 'keydown', bind(this, this.handleKeyPress));
  12600. }
  12601. /**
  12602. * Called when a `MenuButton` loses focus. Turns off the listener for
  12603. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  12604. *
  12605. * @param {EventTarget~Event} event
  12606. * The `blur` event that caused this function to be called.
  12607. *
  12608. * @listens blur
  12609. */
  12610. ;
  12611. _proto.handleBlur = function handleBlur() {
  12612. off(document, 'keydown', bind(this, this.handleKeyPress));
  12613. }
  12614. /**
  12615. * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
  12616. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  12617. *
  12618. * @param {EventTarget~Event} event
  12619. * The `keydown` event that caused this function to be called.
  12620. *
  12621. * @listens keydown
  12622. */
  12623. ;
  12624. _proto.handleKeyPress = function handleKeyPress(event) {
  12625. // Escape or Tab unpress the 'button'
  12626. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  12627. if (this.buttonPressed_) {
  12628. this.unpressButton();
  12629. } // Don't preventDefault for Tab key - we still want to lose focus
  12630. if (!keycode.isEventKey(event, 'Tab')) {
  12631. event.preventDefault(); // Set focus back to the menu button's button
  12632. this.menuButton_.focus();
  12633. } // Up Arrow or Down Arrow also 'press' the button to open the menu
  12634. } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
  12635. if (!this.buttonPressed_) {
  12636. event.preventDefault();
  12637. this.pressButton();
  12638. }
  12639. }
  12640. }
  12641. /**
  12642. * Handle a `keydown` event on a sub-menu. The listener for this is added in
  12643. * the constructor.
  12644. *
  12645. * @param {EventTarget~Event} event
  12646. * Key press event
  12647. *
  12648. * @listens keydown
  12649. */
  12650. ;
  12651. _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
  12652. // Escape or Tab unpress the 'button'
  12653. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  12654. if (this.buttonPressed_) {
  12655. this.unpressButton();
  12656. } // Don't preventDefault for Tab key - we still want to lose focus
  12657. if (!keycode.isEventKey(event, 'Tab')) {
  12658. event.preventDefault(); // Set focus back to the menu button's button
  12659. this.menuButton_.focus();
  12660. }
  12661. }
  12662. }
  12663. /**
  12664. * Put the current `MenuButton` into a pressed state.
  12665. */
  12666. ;
  12667. _proto.pressButton = function pressButton() {
  12668. if (this.enabled_) {
  12669. this.buttonPressed_ = true;
  12670. this.menu.show();
  12671. this.menu.lockShowing();
  12672. this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
  12673. // undesired scrolling behavior when the player is in an iframe
  12674. if (IS_IOS && isInFrame()) {
  12675. // Return early so that the menu isn't focused
  12676. return;
  12677. }
  12678. this.menu.focus();
  12679. }
  12680. }
  12681. /**
  12682. * Take the current `MenuButton` out of a pressed state.
  12683. */
  12684. ;
  12685. _proto.unpressButton = function unpressButton() {
  12686. if (this.enabled_) {
  12687. this.buttonPressed_ = false;
  12688. this.menu.unlockShowing();
  12689. this.menu.hide();
  12690. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  12691. }
  12692. }
  12693. /**
  12694. * Disable the `MenuButton`. Don't allow it to be clicked.
  12695. */
  12696. ;
  12697. _proto.disable = function disable() {
  12698. this.unpressButton();
  12699. this.enabled_ = false;
  12700. this.addClass('vjs-disabled');
  12701. this.menuButton_.disable();
  12702. }
  12703. /**
  12704. * Enable the `MenuButton`. Allow it to be clicked.
  12705. */
  12706. ;
  12707. _proto.enable = function enable() {
  12708. this.enabled_ = true;
  12709. this.removeClass('vjs-disabled');
  12710. this.menuButton_.enable();
  12711. };
  12712. return MenuButton;
  12713. }(Component);
  12714. Component.registerComponent('MenuButton', MenuButton);
  12715. /**
  12716. * The base class for buttons that toggle specific track types (e.g. subtitles).
  12717. *
  12718. * @extends MenuButton
  12719. */
  12720. var TrackButton =
  12721. /*#__PURE__*/
  12722. function (_MenuButton) {
  12723. _inheritsLoose(TrackButton, _MenuButton);
  12724. /**
  12725. * Creates an instance of this class.
  12726. *
  12727. * @param {Player} player
  12728. * The `Player` that this class should be attached to.
  12729. *
  12730. * @param {Object} [options]
  12731. * The key/value store of player options.
  12732. */
  12733. function TrackButton(player, options) {
  12734. var _this;
  12735. var tracks = options.tracks;
  12736. _this = _MenuButton.call(this, player, options) || this;
  12737. if (_this.items.length <= 1) {
  12738. _this.hide();
  12739. }
  12740. if (!tracks) {
  12741. return _assertThisInitialized(_this);
  12742. }
  12743. var updateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update);
  12744. tracks.addEventListener('removetrack', updateHandler);
  12745. tracks.addEventListener('addtrack', updateHandler);
  12746. _this.player_.on('ready', updateHandler);
  12747. _this.player_.on('dispose', function () {
  12748. tracks.removeEventListener('removetrack', updateHandler);
  12749. tracks.removeEventListener('addtrack', updateHandler);
  12750. });
  12751. return _this;
  12752. }
  12753. return TrackButton;
  12754. }(MenuButton);
  12755. Component.registerComponent('TrackButton', TrackButton);
  12756. /**
  12757. * @file menu-keys.js
  12758. */
  12759. /**
  12760. * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
  12761. * Note that 'Enter' and 'Space' are not included here (otherwise they would
  12762. * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
  12763. * @typedef MenuKeys
  12764. * @array
  12765. */
  12766. var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
  12767. /**
  12768. * The component for a menu item. `<li>`
  12769. *
  12770. * @extends ClickableComponent
  12771. */
  12772. var MenuItem =
  12773. /*#__PURE__*/
  12774. function (_ClickableComponent) {
  12775. _inheritsLoose(MenuItem, _ClickableComponent);
  12776. /**
  12777. * Creates an instance of the this class.
  12778. *
  12779. * @param {Player} player
  12780. * The `Player` that this class should be attached to.
  12781. *
  12782. * @param {Object} [options={}]
  12783. * The key/value store of player options.
  12784. *
  12785. */
  12786. function MenuItem(player, options) {
  12787. var _this;
  12788. _this = _ClickableComponent.call(this, player, options) || this;
  12789. _this.selectable = options.selectable;
  12790. _this.isSelected_ = options.selected || false;
  12791. _this.multiSelectable = options.multiSelectable;
  12792. _this.selected(_this.isSelected_);
  12793. if (_this.selectable) {
  12794. if (_this.multiSelectable) {
  12795. _this.el_.setAttribute('role', 'menuitemcheckbox');
  12796. } else {
  12797. _this.el_.setAttribute('role', 'menuitemradio');
  12798. }
  12799. } else {
  12800. _this.el_.setAttribute('role', 'menuitem');
  12801. }
  12802. return _this;
  12803. }
  12804. /**
  12805. * Create the `MenuItem's DOM element
  12806. *
  12807. * @param {string} [type=li]
  12808. * Element's node type, not actually used, always set to `li`.
  12809. *
  12810. * @param {Object} [props={}]
  12811. * An object of properties that should be set on the element
  12812. *
  12813. * @param {Object} [attrs={}]
  12814. * An object of attributes that should be set on the element
  12815. *
  12816. * @return {Element}
  12817. * The element that gets created.
  12818. */
  12819. var _proto = MenuItem.prototype;
  12820. _proto.createEl = function createEl(type, props, attrs) {
  12821. // The control is textual, not just an icon
  12822. this.nonIconControl = true;
  12823. return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
  12824. className: 'vjs-menu-item',
  12825. innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>",
  12826. tabIndex: -1
  12827. }, props), attrs);
  12828. }
  12829. /**
  12830. * Ignore keys which are used by the menu, but pass any other ones up. See
  12831. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  12832. *
  12833. * @param {EventTarget~Event} event
  12834. * The `keydown` event that caused this function to be called.
  12835. *
  12836. * @listens keydown
  12837. */
  12838. ;
  12839. _proto.handleKeyPress = function handleKeyPress(event) {
  12840. if (!MenuKeys.some(function (key) {
  12841. return keycode.isEventKey(event, key);
  12842. })) {
  12843. // Pass keypress handling up for unused keys
  12844. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  12845. }
  12846. }
  12847. /**
  12848. * Any click on a `MenuItem` puts it into the selected state.
  12849. * See {@link ClickableComponent#handleClick} for instances where this is called.
  12850. *
  12851. * @param {EventTarget~Event} event
  12852. * The `keydown`, `tap`, or `click` event that caused this function to be
  12853. * called.
  12854. *
  12855. * @listens tap
  12856. * @listens click
  12857. */
  12858. ;
  12859. _proto.handleClick = function handleClick(event) {
  12860. this.selected(true);
  12861. }
  12862. /**
  12863. * Set the state for this menu item as selected or not.
  12864. *
  12865. * @param {boolean} selected
  12866. * if the menu item is selected or not
  12867. */
  12868. ;
  12869. _proto.selected = function selected(_selected) {
  12870. if (this.selectable) {
  12871. if (_selected) {
  12872. this.addClass('vjs-selected');
  12873. this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
  12874. // so indicate selected state to screen reader in the control text.
  12875. this.controlText(', selected');
  12876. this.isSelected_ = true;
  12877. } else {
  12878. this.removeClass('vjs-selected');
  12879. this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
  12880. this.controlText('');
  12881. this.isSelected_ = false;
  12882. }
  12883. }
  12884. };
  12885. return MenuItem;
  12886. }(ClickableComponent);
  12887. Component.registerComponent('MenuItem', MenuItem);
  12888. /**
  12889. * The specific menu item type for selecting a language within a text track kind
  12890. *
  12891. * @extends MenuItem
  12892. */
  12893. var TextTrackMenuItem =
  12894. /*#__PURE__*/
  12895. function (_MenuItem) {
  12896. _inheritsLoose(TextTrackMenuItem, _MenuItem);
  12897. /**
  12898. * Creates an instance of this class.
  12899. *
  12900. * @param {Player} player
  12901. * The `Player` that this class should be attached to.
  12902. *
  12903. * @param {Object} [options]
  12904. * The key/value store of player options.
  12905. */
  12906. function TextTrackMenuItem(player, options) {
  12907. var _this;
  12908. var track = options.track;
  12909. var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
  12910. options.label = track.label || track.language || 'Unknown';
  12911. options.selected = track.mode === 'showing';
  12912. _this = _MenuItem.call(this, player, options) || this;
  12913. _this.track = track;
  12914. var changeHandler = function changeHandler() {
  12915. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  12916. args[_key] = arguments[_key];
  12917. }
  12918. _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  12919. };
  12920. var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
  12921. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  12922. args[_key2] = arguments[_key2];
  12923. }
  12924. _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  12925. };
  12926. player.on(['loadstart', 'texttrackchange'], changeHandler);
  12927. tracks.addEventListener('change', changeHandler);
  12928. tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  12929. _this.on('dispose', function () {
  12930. player.off(['loadstart', 'texttrackchange'], changeHandler);
  12931. tracks.removeEventListener('change', changeHandler);
  12932. tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  12933. }); // iOS7 doesn't dispatch change events to TextTrackLists when an
  12934. // associated track's mode changes. Without something like
  12935. // Object.observe() (also not present on iOS7), it's not
  12936. // possible to detect changes to the mode attribute and polyfill
  12937. // the change event. As a poor substitute, we manually dispatch
  12938. // change events whenever the controls modify the mode.
  12939. if (tracks.onchange === undefined) {
  12940. var event;
  12941. _this.on(['tap', 'click'], function () {
  12942. if (typeof window$1.Event !== 'object') {
  12943. // Android 2.3 throws an Illegal Constructor error for window.Event
  12944. try {
  12945. event = new window$1.Event('change');
  12946. } catch (err) {// continue regardless of error
  12947. }
  12948. }
  12949. if (!event) {
  12950. event = document.createEvent('Event');
  12951. event.initEvent('change', true, true);
  12952. }
  12953. tracks.dispatchEvent(event);
  12954. });
  12955. } // set the default state based on current tracks
  12956. _this.handleTracksChange();
  12957. return _this;
  12958. }
  12959. /**
  12960. * This gets called when an `TextTrackMenuItem` is "clicked". See
  12961. * {@link ClickableComponent} for more detailed information on what a click can be.
  12962. *
  12963. * @param {EventTarget~Event} event
  12964. * The `keydown`, `tap`, or `click` event that caused this function to be
  12965. * called.
  12966. *
  12967. * @listens tap
  12968. * @listens click
  12969. */
  12970. var _proto = TextTrackMenuItem.prototype;
  12971. _proto.handleClick = function handleClick(event) {
  12972. var referenceTrack = this.track;
  12973. var tracks = this.player_.textTracks();
  12974. _MenuItem.prototype.handleClick.call(this, event);
  12975. if (!tracks) {
  12976. return;
  12977. } // Determine the relevant kind(s) of tracks for this component and filter
  12978. // out empty kinds.
  12979. var kinds = (referenceTrack.kinds || [referenceTrack.kind]).filter(Boolean);
  12980. for (var i = 0; i < tracks.length; i++) {
  12981. var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
  12982. // skip it. We do not want to affect tracks of incompatible kind(s).
  12983. if (kinds.indexOf(track.kind) === -1) {
  12984. continue;
  12985. } // If this text track is the component's track and it is not showing,
  12986. // set it to showing.
  12987. if (track === referenceTrack) {
  12988. if (track.mode !== 'showing') {
  12989. track.mode = 'showing';
  12990. } // If this text track is not the component's track and it is not
  12991. // disabled, set it to disabled.
  12992. } else if (track.mode !== 'disabled') {
  12993. track.mode = 'disabled';
  12994. }
  12995. }
  12996. }
  12997. /**
  12998. * Handle text track list change
  12999. *
  13000. * @param {EventTarget~Event} event
  13001. * The `change` event that caused this function to be called.
  13002. *
  13003. * @listens TextTrackList#change
  13004. */
  13005. ;
  13006. _proto.handleTracksChange = function handleTracksChange(event) {
  13007. var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
  13008. // screen readers to read the appended control text unnecessarily
  13009. if (shouldBeSelected !== this.isSelected_) {
  13010. this.selected(shouldBeSelected);
  13011. }
  13012. };
  13013. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  13014. if (this.track.mode === 'showing') {
  13015. var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
  13016. if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
  13017. return;
  13018. }
  13019. this.player_.cache_.selectedLanguage = {
  13020. enabled: true,
  13021. language: this.track.language,
  13022. kind: this.track.kind
  13023. };
  13024. }
  13025. };
  13026. _proto.dispose = function dispose() {
  13027. // remove reference to track object on dispose
  13028. this.track = null;
  13029. _MenuItem.prototype.dispose.call(this);
  13030. };
  13031. return TextTrackMenuItem;
  13032. }(MenuItem);
  13033. Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
  13034. /**
  13035. * A special menu item for turning of a specific type of text track
  13036. *
  13037. * @extends TextTrackMenuItem
  13038. */
  13039. var OffTextTrackMenuItem =
  13040. /*#__PURE__*/
  13041. function (_TextTrackMenuItem) {
  13042. _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
  13043. /**
  13044. * Creates an instance of this class.
  13045. *
  13046. * @param {Player} player
  13047. * The `Player` that this class should be attached to.
  13048. *
  13049. * @param {Object} [options]
  13050. * The key/value store of player options.
  13051. */
  13052. function OffTextTrackMenuItem(player, options) {
  13053. // Create pseudo track info
  13054. // Requires options['kind']
  13055. options.track = {
  13056. player: player,
  13057. kind: options.kind,
  13058. kinds: options.kinds,
  13059. default: false,
  13060. mode: 'disabled'
  13061. };
  13062. if (!options.kinds) {
  13063. options.kinds = [options.kind];
  13064. }
  13065. if (options.label) {
  13066. options.track.label = options.label;
  13067. } else {
  13068. options.track.label = options.kinds.join(' and ') + ' off';
  13069. } // MenuItem is selectable
  13070. options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  13071. options.multiSelectable = false;
  13072. return _TextTrackMenuItem.call(this, player, options) || this;
  13073. }
  13074. /**
  13075. * Handle text track change
  13076. *
  13077. * @param {EventTarget~Event} event
  13078. * The event that caused this function to run
  13079. */
  13080. var _proto = OffTextTrackMenuItem.prototype;
  13081. _proto.handleTracksChange = function handleTracksChange(event) {
  13082. var tracks = this.player().textTracks();
  13083. var shouldBeSelected = true;
  13084. for (var i = 0, l = tracks.length; i < l; i++) {
  13085. var track = tracks[i];
  13086. if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
  13087. shouldBeSelected = false;
  13088. break;
  13089. }
  13090. } // Prevent redundant selected() calls because they may cause
  13091. // screen readers to read the appended control text unnecessarily
  13092. if (shouldBeSelected !== this.isSelected_) {
  13093. this.selected(shouldBeSelected);
  13094. }
  13095. };
  13096. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  13097. var tracks = this.player().textTracks();
  13098. var allHidden = true;
  13099. for (var i = 0, l = tracks.length; i < l; i++) {
  13100. var track = tracks[i];
  13101. if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
  13102. allHidden = false;
  13103. break;
  13104. }
  13105. }
  13106. if (allHidden) {
  13107. this.player_.cache_.selectedLanguage = {
  13108. enabled: false
  13109. };
  13110. }
  13111. };
  13112. return OffTextTrackMenuItem;
  13113. }(TextTrackMenuItem);
  13114. Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
  13115. /**
  13116. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  13117. *
  13118. * @extends MenuButton
  13119. */
  13120. var TextTrackButton =
  13121. /*#__PURE__*/
  13122. function (_TrackButton) {
  13123. _inheritsLoose(TextTrackButton, _TrackButton);
  13124. /**
  13125. * Creates an instance of this class.
  13126. *
  13127. * @param {Player} player
  13128. * The `Player` that this class should be attached to.
  13129. *
  13130. * @param {Object} [options={}]
  13131. * The key/value store of player options.
  13132. */
  13133. function TextTrackButton(player, options) {
  13134. if (options === void 0) {
  13135. options = {};
  13136. }
  13137. options.tracks = player.textTracks();
  13138. return _TrackButton.call(this, player, options) || this;
  13139. }
  13140. /**
  13141. * Create a menu item for each text track
  13142. *
  13143. * @param {TextTrackMenuItem[]} [items=[]]
  13144. * Existing array of items to use during creation
  13145. *
  13146. * @return {TextTrackMenuItem[]}
  13147. * Array of menu items that were created
  13148. */
  13149. var _proto = TextTrackButton.prototype;
  13150. _proto.createItems = function createItems(items, TrackMenuItem) {
  13151. if (items === void 0) {
  13152. items = [];
  13153. }
  13154. if (TrackMenuItem === void 0) {
  13155. TrackMenuItem = TextTrackMenuItem;
  13156. }
  13157. // Label is an override for the [track] off label
  13158. // USed to localise captions/subtitles
  13159. var label;
  13160. if (this.label_) {
  13161. label = this.label_ + " off";
  13162. } // Add an OFF menu item to turn all tracks off
  13163. items.push(new OffTextTrackMenuItem(this.player_, {
  13164. kinds: this.kinds_,
  13165. kind: this.kind_,
  13166. label: label
  13167. }));
  13168. this.hideThreshold_ += 1;
  13169. var tracks = this.player_.textTracks();
  13170. if (!Array.isArray(this.kinds_)) {
  13171. this.kinds_ = [this.kind_];
  13172. }
  13173. for (var i = 0; i < tracks.length; i++) {
  13174. var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
  13175. if (this.kinds_.indexOf(track.kind) > -1) {
  13176. var item = new TrackMenuItem(this.player_, {
  13177. track: track,
  13178. // MenuItem is selectable
  13179. selectable: true,
  13180. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  13181. multiSelectable: false
  13182. });
  13183. item.addClass("vjs-" + track.kind + "-menu-item");
  13184. items.push(item);
  13185. }
  13186. }
  13187. return items;
  13188. };
  13189. return TextTrackButton;
  13190. }(TrackButton);
  13191. Component.registerComponent('TextTrackButton', TextTrackButton);
  13192. /**
  13193. * The chapter track menu item
  13194. *
  13195. * @extends MenuItem
  13196. */
  13197. var ChaptersTrackMenuItem =
  13198. /*#__PURE__*/
  13199. function (_MenuItem) {
  13200. _inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
  13201. /**
  13202. * Creates an instance of this class.
  13203. *
  13204. * @param {Player} player
  13205. * The `Player` that this class should be attached to.
  13206. *
  13207. * @param {Object} [options]
  13208. * The key/value store of player options.
  13209. */
  13210. function ChaptersTrackMenuItem(player, options) {
  13211. var _this;
  13212. var track = options.track;
  13213. var cue = options.cue;
  13214. var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
  13215. options.selectable = true;
  13216. options.multiSelectable = false;
  13217. options.label = cue.text;
  13218. options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
  13219. _this = _MenuItem.call(this, player, options) || this;
  13220. _this.track = track;
  13221. _this.cue = cue;
  13222. track.addEventListener('cuechange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
  13223. return _this;
  13224. }
  13225. /**
  13226. * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
  13227. * {@link ClickableComponent} for more detailed information on what a click can be.
  13228. *
  13229. * @param {EventTarget~Event} [event]
  13230. * The `keydown`, `tap`, or `click` event that caused this function to be
  13231. * called.
  13232. *
  13233. * @listens tap
  13234. * @listens click
  13235. */
  13236. var _proto = ChaptersTrackMenuItem.prototype;
  13237. _proto.handleClick = function handleClick(event) {
  13238. _MenuItem.prototype.handleClick.call(this);
  13239. this.player_.currentTime(this.cue.startTime);
  13240. this.update(this.cue.startTime);
  13241. }
  13242. /**
  13243. * Update chapter menu item
  13244. *
  13245. * @param {EventTarget~Event} [event]
  13246. * The `cuechange` event that caused this function to run.
  13247. *
  13248. * @listens TextTrack#cuechange
  13249. */
  13250. ;
  13251. _proto.update = function update(event) {
  13252. var cue = this.cue;
  13253. var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
  13254. this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
  13255. };
  13256. return ChaptersTrackMenuItem;
  13257. }(MenuItem);
  13258. Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
  13259. /**
  13260. * The button component for toggling and selecting chapters
  13261. * Chapters act much differently than other text tracks
  13262. * Cues are navigation vs. other tracks of alternative languages
  13263. *
  13264. * @extends TextTrackButton
  13265. */
  13266. var ChaptersButton =
  13267. /*#__PURE__*/
  13268. function (_TextTrackButton) {
  13269. _inheritsLoose(ChaptersButton, _TextTrackButton);
  13270. /**
  13271. * Creates an instance of this class.
  13272. *
  13273. * @param {Player} player
  13274. * The `Player` that this class should be attached to.
  13275. *
  13276. * @param {Object} [options]
  13277. * The key/value store of player options.
  13278. *
  13279. * @param {Component~ReadyCallback} [ready]
  13280. * The function to call when this function is ready.
  13281. */
  13282. function ChaptersButton(player, options, ready) {
  13283. return _TextTrackButton.call(this, player, options, ready) || this;
  13284. }
  13285. /**
  13286. * Builds the default DOM `className`.
  13287. *
  13288. * @return {string}
  13289. * The DOM `className` for this object.
  13290. */
  13291. var _proto = ChaptersButton.prototype;
  13292. _proto.buildCSSClass = function buildCSSClass() {
  13293. return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13294. };
  13295. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13296. return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13297. }
  13298. /**
  13299. * Update the menu based on the current state of its items.
  13300. *
  13301. * @param {EventTarget~Event} [event]
  13302. * An event that triggered this function to run.
  13303. *
  13304. * @listens TextTrackList#addtrack
  13305. * @listens TextTrackList#removetrack
  13306. * @listens TextTrackList#change
  13307. */
  13308. ;
  13309. _proto.update = function update(event) {
  13310. if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
  13311. this.setTrack(this.findChaptersTrack());
  13312. }
  13313. _TextTrackButton.prototype.update.call(this);
  13314. }
  13315. /**
  13316. * Set the currently selected track for the chapters button.
  13317. *
  13318. * @param {TextTrack} track
  13319. * The new track to select. Nothing will change if this is the currently selected
  13320. * track.
  13321. */
  13322. ;
  13323. _proto.setTrack = function setTrack(track) {
  13324. if (this.track_ === track) {
  13325. return;
  13326. }
  13327. if (!this.updateHandler_) {
  13328. this.updateHandler_ = this.update.bind(this);
  13329. } // here this.track_ refers to the old track instance
  13330. if (this.track_) {
  13331. var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  13332. if (remoteTextTrackEl) {
  13333. remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
  13334. }
  13335. this.track_ = null;
  13336. }
  13337. this.track_ = track; // here this.track_ refers to the new track instance
  13338. if (this.track_) {
  13339. this.track_.mode = 'hidden';
  13340. var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  13341. if (_remoteTextTrackEl) {
  13342. _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
  13343. }
  13344. }
  13345. }
  13346. /**
  13347. * Find the track object that is currently in use by this ChaptersButton
  13348. *
  13349. * @return {TextTrack|undefined}
  13350. * The current track or undefined if none was found.
  13351. */
  13352. ;
  13353. _proto.findChaptersTrack = function findChaptersTrack() {
  13354. var tracks = this.player_.textTracks() || [];
  13355. for (var i = tracks.length - 1; i >= 0; i--) {
  13356. // We will always choose the last track as our chaptersTrack
  13357. var track = tracks[i];
  13358. if (track.kind === this.kind_) {
  13359. return track;
  13360. }
  13361. }
  13362. }
  13363. /**
  13364. * Get the caption for the ChaptersButton based on the track label. This will also
  13365. * use the current tracks localized kind as a fallback if a label does not exist.
  13366. *
  13367. * @return {string}
  13368. * The tracks current label or the localized track kind.
  13369. */
  13370. ;
  13371. _proto.getMenuCaption = function getMenuCaption() {
  13372. if (this.track_ && this.track_.label) {
  13373. return this.track_.label;
  13374. }
  13375. return this.localize(toTitleCase(this.kind_));
  13376. }
  13377. /**
  13378. * Create menu from chapter track
  13379. *
  13380. * @return {Menu}
  13381. * New menu for the chapter buttons
  13382. */
  13383. ;
  13384. _proto.createMenu = function createMenu() {
  13385. this.options_.title = this.getMenuCaption();
  13386. return _TextTrackButton.prototype.createMenu.call(this);
  13387. }
  13388. /**
  13389. * Create a menu item for each text track
  13390. *
  13391. * @return {TextTrackMenuItem[]}
  13392. * Array of menu items
  13393. */
  13394. ;
  13395. _proto.createItems = function createItems() {
  13396. var items = [];
  13397. if (!this.track_) {
  13398. return items;
  13399. }
  13400. var cues = this.track_.cues;
  13401. if (!cues) {
  13402. return items;
  13403. }
  13404. for (var i = 0, l = cues.length; i < l; i++) {
  13405. var cue = cues[i];
  13406. var mi = new ChaptersTrackMenuItem(this.player_, {
  13407. track: this.track_,
  13408. cue: cue
  13409. });
  13410. items.push(mi);
  13411. }
  13412. return items;
  13413. };
  13414. return ChaptersButton;
  13415. }(TextTrackButton);
  13416. /**
  13417. * `kind` of TextTrack to look for to associate it with this menu.
  13418. *
  13419. * @type {string}
  13420. * @private
  13421. */
  13422. ChaptersButton.prototype.kind_ = 'chapters';
  13423. /**
  13424. * The text that should display over the `ChaptersButton`s controls. Added for localization.
  13425. *
  13426. * @type {string}
  13427. * @private
  13428. */
  13429. ChaptersButton.prototype.controlText_ = 'Chapters';
  13430. Component.registerComponent('ChaptersButton', ChaptersButton);
  13431. /**
  13432. * The button component for toggling and selecting descriptions
  13433. *
  13434. * @extends TextTrackButton
  13435. */
  13436. var DescriptionsButton =
  13437. /*#__PURE__*/
  13438. function (_TextTrackButton) {
  13439. _inheritsLoose(DescriptionsButton, _TextTrackButton);
  13440. /**
  13441. * Creates an instance of this class.
  13442. *
  13443. * @param {Player} player
  13444. * The `Player` that this class should be attached to.
  13445. *
  13446. * @param {Object} [options]
  13447. * The key/value store of player options.
  13448. *
  13449. * @param {Component~ReadyCallback} [ready]
  13450. * The function to call when this component is ready.
  13451. */
  13452. function DescriptionsButton(player, options, ready) {
  13453. var _this;
  13454. _this = _TextTrackButton.call(this, player, options, ready) || this;
  13455. var tracks = player.textTracks();
  13456. var changeHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTracksChange);
  13457. tracks.addEventListener('change', changeHandler);
  13458. _this.on('dispose', function () {
  13459. tracks.removeEventListener('change', changeHandler);
  13460. });
  13461. return _this;
  13462. }
  13463. /**
  13464. * Handle text track change
  13465. *
  13466. * @param {EventTarget~Event} event
  13467. * The event that caused this function to run
  13468. *
  13469. * @listens TextTrackList#change
  13470. */
  13471. var _proto = DescriptionsButton.prototype;
  13472. _proto.handleTracksChange = function handleTracksChange(event) {
  13473. var tracks = this.player().textTracks();
  13474. var disabled = false; // Check whether a track of a different kind is showing
  13475. for (var i = 0, l = tracks.length; i < l; i++) {
  13476. var track = tracks[i];
  13477. if (track.kind !== this.kind_ && track.mode === 'showing') {
  13478. disabled = true;
  13479. break;
  13480. }
  13481. } // If another track is showing, disable this menu button
  13482. if (disabled) {
  13483. this.disable();
  13484. } else {
  13485. this.enable();
  13486. }
  13487. }
  13488. /**
  13489. * Builds the default DOM `className`.
  13490. *
  13491. * @return {string}
  13492. * The DOM `className` for this object.
  13493. */
  13494. ;
  13495. _proto.buildCSSClass = function buildCSSClass() {
  13496. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13497. };
  13498. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13499. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13500. };
  13501. return DescriptionsButton;
  13502. }(TextTrackButton);
  13503. /**
  13504. * `kind` of TextTrack to look for to associate it with this menu.
  13505. *
  13506. * @type {string}
  13507. * @private
  13508. */
  13509. DescriptionsButton.prototype.kind_ = 'descriptions';
  13510. /**
  13511. * The text that should display over the `DescriptionsButton`s controls. Added for localization.
  13512. *
  13513. * @type {string}
  13514. * @private
  13515. */
  13516. DescriptionsButton.prototype.controlText_ = 'Descriptions';
  13517. Component.registerComponent('DescriptionsButton', DescriptionsButton);
  13518. /**
  13519. * The button component for toggling and selecting subtitles
  13520. *
  13521. * @extends TextTrackButton
  13522. */
  13523. var SubtitlesButton =
  13524. /*#__PURE__*/
  13525. function (_TextTrackButton) {
  13526. _inheritsLoose(SubtitlesButton, _TextTrackButton);
  13527. /**
  13528. * Creates an instance of this class.
  13529. *
  13530. * @param {Player} player
  13531. * The `Player` that this class should be attached to.
  13532. *
  13533. * @param {Object} [options]
  13534. * The key/value store of player options.
  13535. *
  13536. * @param {Component~ReadyCallback} [ready]
  13537. * The function to call when this component is ready.
  13538. */
  13539. function SubtitlesButton(player, options, ready) {
  13540. return _TextTrackButton.call(this, player, options, ready) || this;
  13541. }
  13542. /**
  13543. * Builds the default DOM `className`.
  13544. *
  13545. * @return {string}
  13546. * The DOM `className` for this object.
  13547. */
  13548. var _proto = SubtitlesButton.prototype;
  13549. _proto.buildCSSClass = function buildCSSClass() {
  13550. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13551. };
  13552. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13553. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13554. };
  13555. return SubtitlesButton;
  13556. }(TextTrackButton);
  13557. /**
  13558. * `kind` of TextTrack to look for to associate it with this menu.
  13559. *
  13560. * @type {string}
  13561. * @private
  13562. */
  13563. SubtitlesButton.prototype.kind_ = 'subtitles';
  13564. /**
  13565. * The text that should display over the `SubtitlesButton`s controls. Added for localization.
  13566. *
  13567. * @type {string}
  13568. * @private
  13569. */
  13570. SubtitlesButton.prototype.controlText_ = 'Subtitles';
  13571. Component.registerComponent('SubtitlesButton', SubtitlesButton);
  13572. /**
  13573. * The menu item for caption track settings menu
  13574. *
  13575. * @extends TextTrackMenuItem
  13576. */
  13577. var CaptionSettingsMenuItem =
  13578. /*#__PURE__*/
  13579. function (_TextTrackMenuItem) {
  13580. _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
  13581. /**
  13582. * Creates an instance of this class.
  13583. *
  13584. * @param {Player} player
  13585. * The `Player` that this class should be attached to.
  13586. *
  13587. * @param {Object} [options]
  13588. * The key/value store of player options.
  13589. */
  13590. function CaptionSettingsMenuItem(player, options) {
  13591. var _this;
  13592. options.track = {
  13593. player: player,
  13594. kind: options.kind,
  13595. label: options.kind + ' settings',
  13596. selectable: false,
  13597. default: false,
  13598. mode: 'disabled'
  13599. }; // CaptionSettingsMenuItem has no concept of 'selected'
  13600. options.selectable = false;
  13601. options.name = 'CaptionSettingsMenuItem';
  13602. _this = _TextTrackMenuItem.call(this, player, options) || this;
  13603. _this.addClass('vjs-texttrack-settings');
  13604. _this.controlText(', opens ' + options.kind + ' settings dialog');
  13605. return _this;
  13606. }
  13607. /**
  13608. * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
  13609. * {@link ClickableComponent} for more detailed information on what a click can be.
  13610. *
  13611. * @param {EventTarget~Event} [event]
  13612. * The `keydown`, `tap`, or `click` event that caused this function to be
  13613. * called.
  13614. *
  13615. * @listens tap
  13616. * @listens click
  13617. */
  13618. var _proto = CaptionSettingsMenuItem.prototype;
  13619. _proto.handleClick = function handleClick(event) {
  13620. this.player().getChild('textTrackSettings').open();
  13621. };
  13622. return CaptionSettingsMenuItem;
  13623. }(TextTrackMenuItem);
  13624. Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
  13625. /**
  13626. * The button component for toggling and selecting captions
  13627. *
  13628. * @extends TextTrackButton
  13629. */
  13630. var CaptionsButton =
  13631. /*#__PURE__*/
  13632. function (_TextTrackButton) {
  13633. _inheritsLoose(CaptionsButton, _TextTrackButton);
  13634. /**
  13635. * Creates an instance of this class.
  13636. *
  13637. * @param {Player} player
  13638. * The `Player` that this class should be attached to.
  13639. *
  13640. * @param {Object} [options]
  13641. * The key/value store of player options.
  13642. *
  13643. * @param {Component~ReadyCallback} [ready]
  13644. * The function to call when this component is ready.
  13645. */
  13646. function CaptionsButton(player, options, ready) {
  13647. return _TextTrackButton.call(this, player, options, ready) || this;
  13648. }
  13649. /**
  13650. * Builds the default DOM `className`.
  13651. *
  13652. * @return {string}
  13653. * The DOM `className` for this object.
  13654. */
  13655. var _proto = CaptionsButton.prototype;
  13656. _proto.buildCSSClass = function buildCSSClass() {
  13657. return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13658. };
  13659. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13660. return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13661. }
  13662. /**
  13663. * Create caption menu items
  13664. *
  13665. * @return {CaptionSettingsMenuItem[]}
  13666. * The array of current menu items.
  13667. */
  13668. ;
  13669. _proto.createItems = function createItems() {
  13670. var items = [];
  13671. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  13672. items.push(new CaptionSettingsMenuItem(this.player_, {
  13673. kind: this.kind_
  13674. }));
  13675. this.hideThreshold_ += 1;
  13676. }
  13677. return _TextTrackButton.prototype.createItems.call(this, items);
  13678. };
  13679. return CaptionsButton;
  13680. }(TextTrackButton);
  13681. /**
  13682. * `kind` of TextTrack to look for to associate it with this menu.
  13683. *
  13684. * @type {string}
  13685. * @private
  13686. */
  13687. CaptionsButton.prototype.kind_ = 'captions';
  13688. /**
  13689. * The text that should display over the `CaptionsButton`s controls. Added for localization.
  13690. *
  13691. * @type {string}
  13692. * @private
  13693. */
  13694. CaptionsButton.prototype.controlText_ = 'Captions';
  13695. Component.registerComponent('CaptionsButton', CaptionsButton);
  13696. /**
  13697. * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
  13698. * in the SubsCapsMenu.
  13699. *
  13700. * @extends TextTrackMenuItem
  13701. */
  13702. var SubsCapsMenuItem =
  13703. /*#__PURE__*/
  13704. function (_TextTrackMenuItem) {
  13705. _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
  13706. function SubsCapsMenuItem() {
  13707. return _TextTrackMenuItem.apply(this, arguments) || this;
  13708. }
  13709. var _proto = SubsCapsMenuItem.prototype;
  13710. _proto.createEl = function createEl(type, props, attrs) {
  13711. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  13712. if (this.options_.track.kind === 'captions') {
  13713. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n ";
  13714. }
  13715. innerHTML += '</span>';
  13716. var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
  13717. innerHTML: innerHTML
  13718. }, props), attrs);
  13719. return el;
  13720. };
  13721. return SubsCapsMenuItem;
  13722. }(TextTrackMenuItem);
  13723. Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
  13724. /**
  13725. * The button component for toggling and selecting captions and/or subtitles
  13726. *
  13727. * @extends TextTrackButton
  13728. */
  13729. var SubsCapsButton =
  13730. /*#__PURE__*/
  13731. function (_TextTrackButton) {
  13732. _inheritsLoose(SubsCapsButton, _TextTrackButton);
  13733. function SubsCapsButton(player, options) {
  13734. var _this;
  13735. if (options === void 0) {
  13736. options = {};
  13737. }
  13738. _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
  13739. // "captions and subtitles" other locales use "subtitles"
  13740. _this.label_ = 'subtitles';
  13741. if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
  13742. _this.label_ = 'captions';
  13743. }
  13744. _this.menuButton_.controlText(toTitleCase(_this.label_));
  13745. return _this;
  13746. }
  13747. /**
  13748. * Builds the default DOM `className`.
  13749. *
  13750. * @return {string}
  13751. * The DOM `className` for this object.
  13752. */
  13753. var _proto = SubsCapsButton.prototype;
  13754. _proto.buildCSSClass = function buildCSSClass() {
  13755. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13756. };
  13757. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13758. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13759. }
  13760. /**
  13761. * Create caption/subtitles menu items
  13762. *
  13763. * @return {CaptionSettingsMenuItem[]}
  13764. * The array of current menu items.
  13765. */
  13766. ;
  13767. _proto.createItems = function createItems() {
  13768. var items = [];
  13769. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  13770. items.push(new CaptionSettingsMenuItem(this.player_, {
  13771. kind: this.label_
  13772. }));
  13773. this.hideThreshold_ += 1;
  13774. }
  13775. items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
  13776. return items;
  13777. };
  13778. return SubsCapsButton;
  13779. }(TextTrackButton);
  13780. /**
  13781. * `kind`s of TextTrack to look for to associate it with this menu.
  13782. *
  13783. * @type {array}
  13784. * @private
  13785. */
  13786. SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
  13787. /**
  13788. * The text that should display over the `SubsCapsButton`s controls.
  13789. *
  13790. *
  13791. * @type {string}
  13792. * @private
  13793. */
  13794. SubsCapsButton.prototype.controlText_ = 'Subtitles';
  13795. Component.registerComponent('SubsCapsButton', SubsCapsButton);
  13796. /**
  13797. * An {@link AudioTrack} {@link MenuItem}
  13798. *
  13799. * @extends MenuItem
  13800. */
  13801. var AudioTrackMenuItem =
  13802. /*#__PURE__*/
  13803. function (_MenuItem) {
  13804. _inheritsLoose(AudioTrackMenuItem, _MenuItem);
  13805. /**
  13806. * Creates an instance of this class.
  13807. *
  13808. * @param {Player} player
  13809. * The `Player` that this class should be attached to.
  13810. *
  13811. * @param {Object} [options]
  13812. * The key/value store of player options.
  13813. */
  13814. function AudioTrackMenuItem(player, options) {
  13815. var _this;
  13816. var track = options.track;
  13817. var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
  13818. options.label = track.label || track.language || 'Unknown';
  13819. options.selected = track.enabled;
  13820. _this = _MenuItem.call(this, player, options) || this;
  13821. _this.track = track;
  13822. _this.addClass("vjs-" + track.kind + "-menu-item");
  13823. var changeHandler = function changeHandler() {
  13824. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  13825. args[_key] = arguments[_key];
  13826. }
  13827. _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  13828. };
  13829. tracks.addEventListener('change', changeHandler);
  13830. _this.on('dispose', function () {
  13831. tracks.removeEventListener('change', changeHandler);
  13832. });
  13833. return _this;
  13834. }
  13835. var _proto = AudioTrackMenuItem.prototype;
  13836. _proto.createEl = function createEl(type, props, attrs) {
  13837. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  13838. if (this.options_.track.kind === 'main-desc') {
  13839. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n ";
  13840. }
  13841. innerHTML += '</span>';
  13842. var el = _MenuItem.prototype.createEl.call(this, type, assign({
  13843. innerHTML: innerHTML
  13844. }, props), attrs);
  13845. return el;
  13846. }
  13847. /**
  13848. * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
  13849. * for more detailed information on what a click can be.
  13850. *
  13851. * @param {EventTarget~Event} [event]
  13852. * The `keydown`, `tap`, or `click` event that caused this function to be
  13853. * called.
  13854. *
  13855. * @listens tap
  13856. * @listens click
  13857. */
  13858. ;
  13859. _proto.handleClick = function handleClick(event) {
  13860. var tracks = this.player_.audioTracks();
  13861. _MenuItem.prototype.handleClick.call(this, event);
  13862. for (var i = 0; i < tracks.length; i++) {
  13863. var track = tracks[i];
  13864. track.enabled = track === this.track;
  13865. }
  13866. }
  13867. /**
  13868. * Handle any {@link AudioTrack} change.
  13869. *
  13870. * @param {EventTarget~Event} [event]
  13871. * The {@link AudioTrackList#change} event that caused this to run.
  13872. *
  13873. * @listens AudioTrackList#change
  13874. */
  13875. ;
  13876. _proto.handleTracksChange = function handleTracksChange(event) {
  13877. this.selected(this.track.enabled);
  13878. };
  13879. return AudioTrackMenuItem;
  13880. }(MenuItem);
  13881. Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
  13882. /**
  13883. * The base class for buttons that toggle specific {@link AudioTrack} types.
  13884. *
  13885. * @extends TrackButton
  13886. */
  13887. var AudioTrackButton =
  13888. /*#__PURE__*/
  13889. function (_TrackButton) {
  13890. _inheritsLoose(AudioTrackButton, _TrackButton);
  13891. /**
  13892. * Creates an instance of this class.
  13893. *
  13894. * @param {Player} player
  13895. * The `Player` that this class should be attached to.
  13896. *
  13897. * @param {Object} [options={}]
  13898. * The key/value store of player options.
  13899. */
  13900. function AudioTrackButton(player, options) {
  13901. if (options === void 0) {
  13902. options = {};
  13903. }
  13904. options.tracks = player.audioTracks();
  13905. return _TrackButton.call(this, player, options) || this;
  13906. }
  13907. /**
  13908. * Builds the default DOM `className`.
  13909. *
  13910. * @return {string}
  13911. * The DOM `className` for this object.
  13912. */
  13913. var _proto = AudioTrackButton.prototype;
  13914. _proto.buildCSSClass = function buildCSSClass() {
  13915. return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
  13916. };
  13917. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13918. return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
  13919. }
  13920. /**
  13921. * Create a menu item for each audio track
  13922. *
  13923. * @param {AudioTrackMenuItem[]} [items=[]]
  13924. * An array of existing menu items to use.
  13925. *
  13926. * @return {AudioTrackMenuItem[]}
  13927. * An array of menu items
  13928. */
  13929. ;
  13930. _proto.createItems = function createItems(items) {
  13931. if (items === void 0) {
  13932. items = [];
  13933. }
  13934. // if there's only one audio track, there no point in showing it
  13935. this.hideThreshold_ = 1;
  13936. var tracks = this.player_.audioTracks();
  13937. for (var i = 0; i < tracks.length; i++) {
  13938. var track = tracks[i];
  13939. items.push(new AudioTrackMenuItem(this.player_, {
  13940. track: track,
  13941. // MenuItem is selectable
  13942. selectable: true,
  13943. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  13944. multiSelectable: false
  13945. }));
  13946. }
  13947. return items;
  13948. };
  13949. return AudioTrackButton;
  13950. }(TrackButton);
  13951. /**
  13952. * The text that should display over the `AudioTrackButton`s controls. Added for localization.
  13953. *
  13954. * @type {string}
  13955. * @private
  13956. */
  13957. AudioTrackButton.prototype.controlText_ = 'Audio Track';
  13958. Component.registerComponent('AudioTrackButton', AudioTrackButton);
  13959. /**
  13960. * The specific menu item type for selecting a playback rate.
  13961. *
  13962. * @extends MenuItem
  13963. */
  13964. var PlaybackRateMenuItem =
  13965. /*#__PURE__*/
  13966. function (_MenuItem) {
  13967. _inheritsLoose(PlaybackRateMenuItem, _MenuItem);
  13968. /**
  13969. * Creates an instance of this class.
  13970. *
  13971. * @param {Player} player
  13972. * The `Player` that this class should be attached to.
  13973. *
  13974. * @param {Object} [options]
  13975. * The key/value store of player options.
  13976. */
  13977. function PlaybackRateMenuItem(player, options) {
  13978. var _this;
  13979. var label = options.rate;
  13980. var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
  13981. options.label = label;
  13982. options.selected = rate === 1;
  13983. options.selectable = true;
  13984. options.multiSelectable = false;
  13985. _this = _MenuItem.call(this, player, options) || this;
  13986. _this.label = label;
  13987. _this.rate = rate;
  13988. _this.on(player, 'ratechange', _this.update);
  13989. return _this;
  13990. }
  13991. /**
  13992. * This gets called when an `PlaybackRateMenuItem` is "clicked". See
  13993. * {@link ClickableComponent} for more detailed information on what a click can be.
  13994. *
  13995. * @param {EventTarget~Event} [event]
  13996. * The `keydown`, `tap`, or `click` event that caused this function to be
  13997. * called.
  13998. *
  13999. * @listens tap
  14000. * @listens click
  14001. */
  14002. var _proto = PlaybackRateMenuItem.prototype;
  14003. _proto.handleClick = function handleClick(event) {
  14004. _MenuItem.prototype.handleClick.call(this);
  14005. this.player().playbackRate(this.rate);
  14006. }
  14007. /**
  14008. * Update the PlaybackRateMenuItem when the playbackrate changes.
  14009. *
  14010. * @param {EventTarget~Event} [event]
  14011. * The `ratechange` event that caused this function to run.
  14012. *
  14013. * @listens Player#ratechange
  14014. */
  14015. ;
  14016. _proto.update = function update(event) {
  14017. this.selected(this.player().playbackRate() === this.rate);
  14018. };
  14019. return PlaybackRateMenuItem;
  14020. }(MenuItem);
  14021. /**
  14022. * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
  14023. *
  14024. * @type {string}
  14025. * @private
  14026. */
  14027. PlaybackRateMenuItem.prototype.contentElType = 'button';
  14028. Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
  14029. /**
  14030. * The component for controlling the playback rate.
  14031. *
  14032. * @extends MenuButton
  14033. */
  14034. var PlaybackRateMenuButton =
  14035. /*#__PURE__*/
  14036. function (_MenuButton) {
  14037. _inheritsLoose(PlaybackRateMenuButton, _MenuButton);
  14038. /**
  14039. * Creates an instance of this class.
  14040. *
  14041. * @param {Player} player
  14042. * The `Player` that this class should be attached to.
  14043. *
  14044. * @param {Object} [options]
  14045. * The key/value store of player options.
  14046. */
  14047. function PlaybackRateMenuButton(player, options) {
  14048. var _this;
  14049. _this = _MenuButton.call(this, player, options) || this;
  14050. _this.updateVisibility();
  14051. _this.updateLabel();
  14052. _this.on(player, 'loadstart', _this.updateVisibility);
  14053. _this.on(player, 'ratechange', _this.updateLabel);
  14054. return _this;
  14055. }
  14056. /**
  14057. * Create the `Component`'s DOM element
  14058. *
  14059. * @return {Element}
  14060. * The element that was created.
  14061. */
  14062. var _proto = PlaybackRateMenuButton.prototype;
  14063. _proto.createEl = function createEl$$1() {
  14064. var el = _MenuButton.prototype.createEl.call(this);
  14065. this.labelEl_ = createEl('div', {
  14066. className: 'vjs-playback-rate-value',
  14067. innerHTML: '1x'
  14068. });
  14069. el.appendChild(this.labelEl_);
  14070. return el;
  14071. };
  14072. _proto.dispose = function dispose() {
  14073. this.labelEl_ = null;
  14074. _MenuButton.prototype.dispose.call(this);
  14075. }
  14076. /**
  14077. * Builds the default DOM `className`.
  14078. *
  14079. * @return {string}
  14080. * The DOM `className` for this object.
  14081. */
  14082. ;
  14083. _proto.buildCSSClass = function buildCSSClass() {
  14084. return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
  14085. };
  14086. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  14087. return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
  14088. }
  14089. /**
  14090. * Create the playback rate menu
  14091. *
  14092. * @return {Menu}
  14093. * Menu object populated with {@link PlaybackRateMenuItem}s
  14094. */
  14095. ;
  14096. _proto.createMenu = function createMenu() {
  14097. var menu = new Menu(this.player());
  14098. var rates = this.playbackRates();
  14099. if (rates) {
  14100. for (var i = rates.length - 1; i >= 0; i--) {
  14101. menu.addChild(new PlaybackRateMenuItem(this.player(), {
  14102. rate: rates[i] + 'x'
  14103. }));
  14104. }
  14105. }
  14106. return menu;
  14107. }
  14108. /**
  14109. * Updates ARIA accessibility attributes
  14110. */
  14111. ;
  14112. _proto.updateARIAAttributes = function updateARIAAttributes() {
  14113. // Current playback rate
  14114. this.el().setAttribute('aria-valuenow', this.player().playbackRate());
  14115. }
  14116. /**
  14117. * This gets called when an `PlaybackRateMenuButton` is "clicked". See
  14118. * {@link ClickableComponent} for more detailed information on what a click can be.
  14119. *
  14120. * @param {EventTarget~Event} [event]
  14121. * The `keydown`, `tap`, or `click` event that caused this function to be
  14122. * called.
  14123. *
  14124. * @listens tap
  14125. * @listens click
  14126. */
  14127. ;
  14128. _proto.handleClick = function handleClick(event) {
  14129. // select next rate option
  14130. var currentRate = this.player().playbackRate();
  14131. var rates = this.playbackRates(); // this will select first one if the last one currently selected
  14132. var newRate = rates[0];
  14133. for (var i = 0; i < rates.length; i++) {
  14134. if (rates[i] > currentRate) {
  14135. newRate = rates[i];
  14136. break;
  14137. }
  14138. }
  14139. this.player().playbackRate(newRate);
  14140. }
  14141. /**
  14142. * Get possible playback rates
  14143. *
  14144. * @return {Array}
  14145. * All possible playback rates
  14146. */
  14147. ;
  14148. _proto.playbackRates = function playbackRates() {
  14149. return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
  14150. }
  14151. /**
  14152. * Get whether playback rates is supported by the tech
  14153. * and an array of playback rates exists
  14154. *
  14155. * @return {boolean}
  14156. * Whether changing playback rate is supported
  14157. */
  14158. ;
  14159. _proto.playbackRateSupported = function playbackRateSupported() {
  14160. return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
  14161. }
  14162. /**
  14163. * Hide playback rate controls when they're no playback rate options to select
  14164. *
  14165. * @param {EventTarget~Event} [event]
  14166. * The event that caused this function to run.
  14167. *
  14168. * @listens Player#loadstart
  14169. */
  14170. ;
  14171. _proto.updateVisibility = function updateVisibility(event) {
  14172. if (this.playbackRateSupported()) {
  14173. this.removeClass('vjs-hidden');
  14174. } else {
  14175. this.addClass('vjs-hidden');
  14176. }
  14177. }
  14178. /**
  14179. * Update button label when rate changed
  14180. *
  14181. * @param {EventTarget~Event} [event]
  14182. * The event that caused this function to run.
  14183. *
  14184. * @listens Player#ratechange
  14185. */
  14186. ;
  14187. _proto.updateLabel = function updateLabel(event) {
  14188. if (this.playbackRateSupported()) {
  14189. this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
  14190. }
  14191. };
  14192. return PlaybackRateMenuButton;
  14193. }(MenuButton);
  14194. /**
  14195. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  14196. *
  14197. * @type {string}
  14198. * @private
  14199. */
  14200. PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
  14201. Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
  14202. /**
  14203. * Just an empty spacer element that can be used as an append point for plugins, etc.
  14204. * Also can be used to create space between elements when necessary.
  14205. *
  14206. * @extends Component
  14207. */
  14208. var Spacer =
  14209. /*#__PURE__*/
  14210. function (_Component) {
  14211. _inheritsLoose(Spacer, _Component);
  14212. function Spacer() {
  14213. return _Component.apply(this, arguments) || this;
  14214. }
  14215. var _proto = Spacer.prototype;
  14216. /**
  14217. * Builds the default DOM `className`.
  14218. *
  14219. * @return {string}
  14220. * The DOM `className` for this object.
  14221. */
  14222. _proto.buildCSSClass = function buildCSSClass() {
  14223. return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
  14224. }
  14225. /**
  14226. * Create the `Component`'s DOM element
  14227. *
  14228. * @return {Element}
  14229. * The element that was created.
  14230. */
  14231. ;
  14232. _proto.createEl = function createEl() {
  14233. return _Component.prototype.createEl.call(this, 'div', {
  14234. className: this.buildCSSClass()
  14235. });
  14236. };
  14237. return Spacer;
  14238. }(Component);
  14239. Component.registerComponent('Spacer', Spacer);
  14240. /**
  14241. * Spacer specifically meant to be used as an insertion point for new plugins, etc.
  14242. *
  14243. * @extends Spacer
  14244. */
  14245. var CustomControlSpacer =
  14246. /*#__PURE__*/
  14247. function (_Spacer) {
  14248. _inheritsLoose(CustomControlSpacer, _Spacer);
  14249. function CustomControlSpacer() {
  14250. return _Spacer.apply(this, arguments) || this;
  14251. }
  14252. var _proto = CustomControlSpacer.prototype;
  14253. /**
  14254. * Builds the default DOM `className`.
  14255. *
  14256. * @return {string}
  14257. * The DOM `className` for this object.
  14258. */
  14259. _proto.buildCSSClass = function buildCSSClass() {
  14260. return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
  14261. }
  14262. /**
  14263. * Create the `Component`'s DOM element
  14264. *
  14265. * @return {Element}
  14266. * The element that was created.
  14267. */
  14268. ;
  14269. _proto.createEl = function createEl() {
  14270. var el = _Spacer.prototype.createEl.call(this, {
  14271. className: this.buildCSSClass()
  14272. }); // No-flex/table-cell mode requires there be some content
  14273. // in the cell to fill the remaining space of the table.
  14274. el.innerHTML = "\xA0";
  14275. return el;
  14276. };
  14277. return CustomControlSpacer;
  14278. }(Spacer);
  14279. Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
  14280. /**
  14281. * Container of main controls.
  14282. *
  14283. * @extends Component
  14284. */
  14285. var ControlBar =
  14286. /*#__PURE__*/
  14287. function (_Component) {
  14288. _inheritsLoose(ControlBar, _Component);
  14289. function ControlBar() {
  14290. return _Component.apply(this, arguments) || this;
  14291. }
  14292. var _proto = ControlBar.prototype;
  14293. /**
  14294. * Create the `Component`'s DOM element
  14295. *
  14296. * @return {Element}
  14297. * The element that was created.
  14298. */
  14299. _proto.createEl = function createEl() {
  14300. return _Component.prototype.createEl.call(this, 'div', {
  14301. className: 'vjs-control-bar',
  14302. dir: 'ltr'
  14303. });
  14304. };
  14305. return ControlBar;
  14306. }(Component);
  14307. /**
  14308. * Default options for `ControlBar`
  14309. *
  14310. * @type {Object}
  14311. * @private
  14312. */
  14313. ControlBar.prototype.options_ = {
  14314. children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
  14315. };
  14316. Component.registerComponent('ControlBar', ControlBar);
  14317. /**
  14318. * A display that indicates an error has occurred. This means that the video
  14319. * is unplayable.
  14320. *
  14321. * @extends ModalDialog
  14322. */
  14323. var ErrorDisplay =
  14324. /*#__PURE__*/
  14325. function (_ModalDialog) {
  14326. _inheritsLoose(ErrorDisplay, _ModalDialog);
  14327. /**
  14328. * Creates an instance of this class.
  14329. *
  14330. * @param {Player} player
  14331. * The `Player` that this class should be attached to.
  14332. *
  14333. * @param {Object} [options]
  14334. * The key/value store of player options.
  14335. */
  14336. function ErrorDisplay(player, options) {
  14337. var _this;
  14338. _this = _ModalDialog.call(this, player, options) || this;
  14339. _this.on(player, 'error', _this.open);
  14340. return _this;
  14341. }
  14342. /**
  14343. * Builds the default DOM `className`.
  14344. *
  14345. * @return {string}
  14346. * The DOM `className` for this object.
  14347. *
  14348. * @deprecated Since version 5.
  14349. */
  14350. var _proto = ErrorDisplay.prototype;
  14351. _proto.buildCSSClass = function buildCSSClass() {
  14352. return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
  14353. }
  14354. /**
  14355. * Gets the localized error message based on the `Player`s error.
  14356. *
  14357. * @return {string}
  14358. * The `Player`s error message localized or an empty string.
  14359. */
  14360. ;
  14361. _proto.content = function content() {
  14362. var error = this.player().error();
  14363. return error ? this.localize(error.message) : '';
  14364. };
  14365. return ErrorDisplay;
  14366. }(ModalDialog);
  14367. /**
  14368. * The default options for an `ErrorDisplay`.
  14369. *
  14370. * @private
  14371. */
  14372. ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
  14373. pauseOnOpen: false,
  14374. fillAlways: true,
  14375. temporary: false,
  14376. uncloseable: true
  14377. });
  14378. Component.registerComponent('ErrorDisplay', ErrorDisplay);
  14379. var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
  14380. var COLOR_BLACK = ['#000', 'Black'];
  14381. var COLOR_BLUE = ['#00F', 'Blue'];
  14382. var COLOR_CYAN = ['#0FF', 'Cyan'];
  14383. var COLOR_GREEN = ['#0F0', 'Green'];
  14384. var COLOR_MAGENTA = ['#F0F', 'Magenta'];
  14385. var COLOR_RED = ['#F00', 'Red'];
  14386. var COLOR_WHITE = ['#FFF', 'White'];
  14387. var COLOR_YELLOW = ['#FF0', 'Yellow'];
  14388. var OPACITY_OPAQUE = ['1', 'Opaque'];
  14389. var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
  14390. var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
  14391. //
  14392. // Possible keys include:
  14393. //
  14394. // `default`:
  14395. // The default option index. Only needs to be provided if not zero.
  14396. // `parser`:
  14397. // A function which is used to parse the value from the selected option in
  14398. // a customized way.
  14399. // `selector`:
  14400. // The selector used to find the associated <select> element.
  14401. var selectConfigs = {
  14402. backgroundColor: {
  14403. selector: '.vjs-bg-color > select',
  14404. id: 'captions-background-color-%s',
  14405. label: 'Color',
  14406. options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  14407. },
  14408. backgroundOpacity: {
  14409. selector: '.vjs-bg-opacity > select',
  14410. id: 'captions-background-opacity-%s',
  14411. label: 'Transparency',
  14412. options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
  14413. },
  14414. color: {
  14415. selector: '.vjs-fg-color > select',
  14416. id: 'captions-foreground-color-%s',
  14417. label: 'Color',
  14418. options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  14419. },
  14420. edgeStyle: {
  14421. selector: '.vjs-edge-style > select',
  14422. id: '%s',
  14423. label: 'Text Edge Style',
  14424. options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
  14425. },
  14426. fontFamily: {
  14427. selector: '.vjs-font-family > select',
  14428. id: 'captions-font-family-%s',
  14429. label: 'Font Family',
  14430. options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
  14431. },
  14432. fontPercent: {
  14433. selector: '.vjs-font-percent > select',
  14434. id: 'captions-font-size-%s',
  14435. label: 'Font Size',
  14436. 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%']],
  14437. default: 2,
  14438. parser: function parser(v) {
  14439. return v === '1.00' ? null : Number(v);
  14440. }
  14441. },
  14442. textOpacity: {
  14443. selector: '.vjs-text-opacity > select',
  14444. id: 'captions-foreground-opacity-%s',
  14445. label: 'Transparency',
  14446. options: [OPACITY_OPAQUE, OPACITY_SEMI]
  14447. },
  14448. // Options for this object are defined below.
  14449. windowColor: {
  14450. selector: '.vjs-window-color > select',
  14451. id: 'captions-window-color-%s',
  14452. label: 'Color'
  14453. },
  14454. // Options for this object are defined below.
  14455. windowOpacity: {
  14456. selector: '.vjs-window-opacity > select',
  14457. id: 'captions-window-opacity-%s',
  14458. label: 'Transparency',
  14459. options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
  14460. }
  14461. };
  14462. selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
  14463. /**
  14464. * Get the actual value of an option.
  14465. *
  14466. * @param {string} value
  14467. * The value to get
  14468. *
  14469. * @param {Function} [parser]
  14470. * Optional function to adjust the value.
  14471. *
  14472. * @return {Mixed}
  14473. * - Will be `undefined` if no value exists
  14474. * - Will be `undefined` if the given value is "none".
  14475. * - Will be the actual value otherwise.
  14476. *
  14477. * @private
  14478. */
  14479. function parseOptionValue(value, parser) {
  14480. if (parser) {
  14481. value = parser(value);
  14482. }
  14483. if (value && value !== 'none') {
  14484. return value;
  14485. }
  14486. }
  14487. /**
  14488. * Gets the value of the selected <option> element within a <select> element.
  14489. *
  14490. * @param {Element} el
  14491. * the element to look in
  14492. *
  14493. * @param {Function} [parser]
  14494. * Optional function to adjust the value.
  14495. *
  14496. * @return {Mixed}
  14497. * - Will be `undefined` if no value exists
  14498. * - Will be `undefined` if the given value is "none".
  14499. * - Will be the actual value otherwise.
  14500. *
  14501. * @private
  14502. */
  14503. function getSelectedOptionValue(el, parser) {
  14504. var value = el.options[el.options.selectedIndex].value;
  14505. return parseOptionValue(value, parser);
  14506. }
  14507. /**
  14508. * Sets the selected <option> element within a <select> element based on a
  14509. * given value.
  14510. *
  14511. * @param {Element} el
  14512. * The element to look in.
  14513. *
  14514. * @param {string} value
  14515. * the property to look on.
  14516. *
  14517. * @param {Function} [parser]
  14518. * Optional function to adjust the value before comparing.
  14519. *
  14520. * @private
  14521. */
  14522. function setSelectedOption(el, value, parser) {
  14523. if (!value) {
  14524. return;
  14525. }
  14526. for (var i = 0; i < el.options.length; i++) {
  14527. if (parseOptionValue(el.options[i].value, parser) === value) {
  14528. el.selectedIndex = i;
  14529. break;
  14530. }
  14531. }
  14532. }
  14533. /**
  14534. * Manipulate Text Tracks settings.
  14535. *
  14536. * @extends ModalDialog
  14537. */
  14538. var TextTrackSettings =
  14539. /*#__PURE__*/
  14540. function (_ModalDialog) {
  14541. _inheritsLoose(TextTrackSettings, _ModalDialog);
  14542. /**
  14543. * Creates an instance of this class.
  14544. *
  14545. * @param {Player} player
  14546. * The `Player` that this class should be attached to.
  14547. *
  14548. * @param {Object} [options]
  14549. * The key/value store of player options.
  14550. */
  14551. function TextTrackSettings(player, options) {
  14552. var _this;
  14553. options.temporary = false;
  14554. _this = _ModalDialog.call(this, player, options) || this;
  14555. _this.updateDisplay = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay); // fill the modal and pretend we have opened it
  14556. _this.fill();
  14557. _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
  14558. _this.endDialog = createEl('p', {
  14559. className: 'vjs-control-text',
  14560. textContent: _this.localize('End of dialog window.')
  14561. });
  14562. _this.el().appendChild(_this.endDialog);
  14563. _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
  14564. if (options.persistTextTrackSettings === undefined) {
  14565. _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
  14566. }
  14567. _this.on(_this.$('.vjs-done-button'), 'click', function () {
  14568. _this.saveSettings();
  14569. _this.close();
  14570. });
  14571. _this.on(_this.$('.vjs-default-button'), 'click', function () {
  14572. _this.setDefaults();
  14573. _this.updateDisplay();
  14574. });
  14575. each(selectConfigs, function (config) {
  14576. _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
  14577. });
  14578. if (_this.options_.persistTextTrackSettings) {
  14579. _this.restoreSettings();
  14580. }
  14581. return _this;
  14582. }
  14583. var _proto = TextTrackSettings.prototype;
  14584. _proto.dispose = function dispose() {
  14585. this.endDialog = null;
  14586. _ModalDialog.prototype.dispose.call(this);
  14587. }
  14588. /**
  14589. * Create a <select> element with configured options.
  14590. *
  14591. * @param {string} key
  14592. * Configuration key to use during creation.
  14593. *
  14594. * @return {string}
  14595. * An HTML string.
  14596. *
  14597. * @private
  14598. */
  14599. ;
  14600. _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
  14601. var _this2 = this;
  14602. if (legendId === void 0) {
  14603. legendId = '';
  14604. }
  14605. if (type === void 0) {
  14606. type = 'label';
  14607. }
  14608. var config = selectConfigs[key];
  14609. var id = config.id.replace('%s', this.id_);
  14610. var selectLabelledbyIds = [legendId, id].join(' ').trim();
  14611. return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
  14612. var optionId = id + '-' + o[1].replace(/\W+/g, '');
  14613. return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
  14614. })).concat('</select>').join('');
  14615. }
  14616. /**
  14617. * Create foreground color element for the component
  14618. *
  14619. * @return {string}
  14620. * An HTML string.
  14621. *
  14622. * @private
  14623. */
  14624. ;
  14625. _proto.createElFgColor_ = function createElFgColor_() {
  14626. var legendId = "captions-text-legend-" + this.id_;
  14627. 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('');
  14628. }
  14629. /**
  14630. * Create background color element for the component
  14631. *
  14632. * @return {string}
  14633. * An HTML string.
  14634. *
  14635. * @private
  14636. */
  14637. ;
  14638. _proto.createElBgColor_ = function createElBgColor_() {
  14639. var legendId = "captions-background-" + this.id_;
  14640. 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('');
  14641. }
  14642. /**
  14643. * Create window color element for the component
  14644. *
  14645. * @return {string}
  14646. * An HTML string.
  14647. *
  14648. * @private
  14649. */
  14650. ;
  14651. _proto.createElWinColor_ = function createElWinColor_() {
  14652. var legendId = "captions-window-" + this.id_;
  14653. 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('');
  14654. }
  14655. /**
  14656. * Create color elements for the component
  14657. *
  14658. * @return {Element}
  14659. * The element that was created
  14660. *
  14661. * @private
  14662. */
  14663. ;
  14664. _proto.createElColors_ = function createElColors_() {
  14665. return createEl('div', {
  14666. className: 'vjs-track-settings-colors',
  14667. innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
  14668. });
  14669. }
  14670. /**
  14671. * Create font elements for the component
  14672. *
  14673. * @return {Element}
  14674. * The element that was created.
  14675. *
  14676. * @private
  14677. */
  14678. ;
  14679. _proto.createElFont_ = function createElFont_() {
  14680. return createEl('div', {
  14681. className: 'vjs-track-settings-font',
  14682. 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('')
  14683. });
  14684. }
  14685. /**
  14686. * Create controls for the component
  14687. *
  14688. * @return {Element}
  14689. * The element that was created.
  14690. *
  14691. * @private
  14692. */
  14693. ;
  14694. _proto.createElControls_ = function createElControls_() {
  14695. var defaultsDescription = this.localize('restore all settings to the default values');
  14696. return createEl('div', {
  14697. className: 'vjs-track-settings-controls',
  14698. 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('')
  14699. });
  14700. };
  14701. _proto.content = function content() {
  14702. return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
  14703. };
  14704. _proto.label = function label() {
  14705. return this.localize('Caption Settings Dialog');
  14706. };
  14707. _proto.description = function description() {
  14708. return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
  14709. };
  14710. _proto.buildCSSClass = function buildCSSClass() {
  14711. return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
  14712. }
  14713. /**
  14714. * Gets an object of text track settings (or null).
  14715. *
  14716. * @return {Object}
  14717. * An object with config values parsed from the DOM or localStorage.
  14718. */
  14719. ;
  14720. _proto.getValues = function getValues() {
  14721. var _this3 = this;
  14722. return reduce(selectConfigs, function (accum, config, key) {
  14723. var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
  14724. if (value !== undefined) {
  14725. accum[key] = value;
  14726. }
  14727. return accum;
  14728. }, {});
  14729. }
  14730. /**
  14731. * Sets text track settings from an object of values.
  14732. *
  14733. * @param {Object} values
  14734. * An object with config values parsed from the DOM or localStorage.
  14735. */
  14736. ;
  14737. _proto.setValues = function setValues(values) {
  14738. var _this4 = this;
  14739. each(selectConfigs, function (config, key) {
  14740. setSelectedOption(_this4.$(config.selector), values[key], config.parser);
  14741. });
  14742. }
  14743. /**
  14744. * Sets all `<select>` elements to their default values.
  14745. */
  14746. ;
  14747. _proto.setDefaults = function setDefaults() {
  14748. var _this5 = this;
  14749. each(selectConfigs, function (config) {
  14750. var index = config.hasOwnProperty('default') ? config.default : 0;
  14751. _this5.$(config.selector).selectedIndex = index;
  14752. });
  14753. }
  14754. /**
  14755. * Restore texttrack settings from localStorage
  14756. */
  14757. ;
  14758. _proto.restoreSettings = function restoreSettings() {
  14759. var values;
  14760. try {
  14761. values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
  14762. } catch (err) {
  14763. log.warn(err);
  14764. }
  14765. if (values) {
  14766. this.setValues(values);
  14767. }
  14768. }
  14769. /**
  14770. * Save text track settings to localStorage
  14771. */
  14772. ;
  14773. _proto.saveSettings = function saveSettings() {
  14774. if (!this.options_.persistTextTrackSettings) {
  14775. return;
  14776. }
  14777. var values = this.getValues();
  14778. try {
  14779. if (Object.keys(values).length) {
  14780. window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
  14781. } else {
  14782. window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
  14783. }
  14784. } catch (err) {
  14785. log.warn(err);
  14786. }
  14787. }
  14788. /**
  14789. * Update display of text track settings
  14790. */
  14791. ;
  14792. _proto.updateDisplay = function updateDisplay() {
  14793. var ttDisplay = this.player_.getChild('textTrackDisplay');
  14794. if (ttDisplay) {
  14795. ttDisplay.updateDisplay();
  14796. }
  14797. }
  14798. /**
  14799. * conditionally blur the element and refocus the captions button
  14800. *
  14801. * @private
  14802. */
  14803. ;
  14804. _proto.conditionalBlur_ = function conditionalBlur_() {
  14805. this.previouslyActiveEl_ = null;
  14806. this.off(document, 'keydown', this.handleKeyDown);
  14807. var cb = this.player_.controlBar;
  14808. var subsCapsBtn = cb && cb.subsCapsButton;
  14809. var ccBtn = cb && cb.captionsButton;
  14810. if (subsCapsBtn) {
  14811. subsCapsBtn.focus();
  14812. } else if (ccBtn) {
  14813. ccBtn.focus();
  14814. }
  14815. };
  14816. return TextTrackSettings;
  14817. }(ModalDialog);
  14818. Component.registerComponent('TextTrackSettings', TextTrackSettings);
  14819. /**
  14820. * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
  14821. *
  14822. * 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}.
  14823. *
  14824. * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
  14825. * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
  14826. * @example <caption>How to disable the resize manager</caption>
  14827. * const player = videojs('#vid', {
  14828. * resizeManager: false
  14829. * });
  14830. *
  14831. * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
  14832. *
  14833. * @extends Component
  14834. */
  14835. var ResizeManager =
  14836. /*#__PURE__*/
  14837. function (_Component) {
  14838. _inheritsLoose(ResizeManager, _Component);
  14839. /**
  14840. * Create the ResizeManager.
  14841. *
  14842. * @param {Object} player
  14843. * The `Player` that this class should be attached to.
  14844. *
  14845. * @param {Object} [options]
  14846. * The key/value store of ResizeManager options.
  14847. *
  14848. * @param {Object} [options.ResizeObserver]
  14849. * A polyfill for ResizeObserver can be passed in here.
  14850. * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
  14851. */
  14852. function ResizeManager(player, options) {
  14853. var _this;
  14854. var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
  14855. if (options.ResizeObserver === null) {
  14856. RESIZE_OBSERVER_AVAILABLE = false;
  14857. } // Only create an element when ResizeObserver isn't available
  14858. var options_ = mergeOptions({
  14859. createEl: !RESIZE_OBSERVER_AVAILABLE,
  14860. reportTouchActivity: false
  14861. }, options);
  14862. _this = _Component.call(this, player, options_) || this;
  14863. _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
  14864. _this.loadListener_ = null;
  14865. _this.resizeObserver_ = null;
  14866. _this.debouncedHandler_ = debounce(function () {
  14867. _this.resizeHandler();
  14868. }, 100, false, _assertThisInitialized(_assertThisInitialized(_this)));
  14869. if (RESIZE_OBSERVER_AVAILABLE) {
  14870. _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
  14871. _this.resizeObserver_.observe(player.el());
  14872. } else {
  14873. _this.loadListener_ = function () {
  14874. if (!_this.el_ || !_this.el_.contentWindow) {
  14875. return;
  14876. }
  14877. var debouncedHandler_ = _this.debouncedHandler_;
  14878. var unloadListener_ = _this.unloadListener_ = function () {
  14879. off(this, 'resize', debouncedHandler_);
  14880. off(this, 'unload', unloadListener_);
  14881. unloadListener_ = null;
  14882. }; // safari and edge can unload the iframe before resizemanager dispose
  14883. // we have to dispose of event handlers correctly before that happens
  14884. on(_this.el_.contentWindow, 'unload', unloadListener_);
  14885. on(_this.el_.contentWindow, 'resize', debouncedHandler_);
  14886. };
  14887. _this.one('load', _this.loadListener_);
  14888. }
  14889. return _this;
  14890. }
  14891. var _proto = ResizeManager.prototype;
  14892. _proto.createEl = function createEl() {
  14893. return _Component.prototype.createEl.call(this, 'iframe', {
  14894. className: 'vjs-resize-manager',
  14895. tabIndex: -1
  14896. }, {
  14897. 'aria-hidden': 'true'
  14898. });
  14899. }
  14900. /**
  14901. * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
  14902. *
  14903. * @fires Player#playerresize
  14904. */
  14905. ;
  14906. _proto.resizeHandler = function resizeHandler() {
  14907. /**
  14908. * Called when the player size has changed
  14909. *
  14910. * @event Player#playerresize
  14911. * @type {EventTarget~Event}
  14912. */
  14913. // make sure player is still around to trigger
  14914. // prevents this from causing an error after dispose
  14915. if (!this.player_ || !this.player_.trigger) {
  14916. return;
  14917. }
  14918. this.player_.trigger('playerresize');
  14919. };
  14920. _proto.dispose = function dispose() {
  14921. if (this.debouncedHandler_) {
  14922. this.debouncedHandler_.cancel();
  14923. }
  14924. if (this.resizeObserver_) {
  14925. if (this.player_.el()) {
  14926. this.resizeObserver_.unobserve(this.player_.el());
  14927. }
  14928. this.resizeObserver_.disconnect();
  14929. }
  14930. if (this.loadListener_) {
  14931. this.off('load', this.loadListener_);
  14932. }
  14933. if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
  14934. this.unloadListener_.call(this.el_.contentWindow);
  14935. }
  14936. this.ResizeObserver = null;
  14937. this.resizeObserver = null;
  14938. this.debouncedHandler_ = null;
  14939. this.loadListener_ = null;
  14940. _Component.prototype.dispose.call(this);
  14941. };
  14942. return ResizeManager;
  14943. }(Component);
  14944. Component.registerComponent('ResizeManager', ResizeManager);
  14945. /* track when we are at the live edge, and other helpers for live playback */
  14946. var LiveTracker =
  14947. /*#__PURE__*/
  14948. function (_Component) {
  14949. _inheritsLoose(LiveTracker, _Component);
  14950. function LiveTracker(player, options) {
  14951. var _this;
  14952. // LiveTracker does not need an element
  14953. var options_ = mergeOptions({
  14954. createEl: false
  14955. }, options);
  14956. _this = _Component.call(this, player, options_) || this;
  14957. _this.reset_();
  14958. _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden,
  14959. // also, tracking when the document is hidden can
  14960. // cause the CPU to spike and eventually crash the page on IE11.
  14961. if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
  14962. _this.on(document, 'visibilitychange', _this.handleVisibilityChange);
  14963. }
  14964. return _this;
  14965. }
  14966. var _proto = LiveTracker.prototype;
  14967. _proto.handleVisibilityChange = function handleVisibilityChange() {
  14968. if (this.player_.duration() !== Infinity) {
  14969. return;
  14970. }
  14971. if (document.hidden) {
  14972. this.stopTracking();
  14973. } else {
  14974. this.startTracking();
  14975. }
  14976. };
  14977. _proto.isBehind_ = function isBehind_() {
  14978. // don't report that we are behind until a timeupdate has been seen
  14979. if (!this.timeupdateSeen_) {
  14980. return false;
  14981. }
  14982. var liveCurrentTime = this.liveCurrentTime();
  14983. var currentTime = this.player_.currentTime();
  14984. var seekableIncrement = this.seekableIncrement_; // the live edge window is the amount of seconds away from live
  14985. // that a player can be, but still be considered live.
  14986. // we add 0.07 because the live tracking happens every 30ms
  14987. // and we want some wiggle room for short segment live playback
  14988. var liveEdgeWindow = seekableIncrement * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd
  14989. // can be Infinity, so we handle that case.
  14990. return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime;
  14991. } // all the functionality for tracking when seek end changes
  14992. // and for tracking how far past seek end we should be
  14993. ;
  14994. _proto.trackLive_ = function trackLive_() {
  14995. this.pastSeekEnd_ = this.pastSeekEnd_;
  14996. var seekable = this.player_.seekable(); // skip undefined seekable
  14997. if (!seekable || !seekable.length) {
  14998. return;
  14999. }
  15000. var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes
  15001. // once we detect that seekable has changed we check the new seek
  15002. // end against current time, with a fudge value of half a second.
  15003. if (newSeekEnd !== this.lastSeekEnd_) {
  15004. if (this.lastSeekEnd_) {
  15005. this.seekableIncrement_ = Math.abs(newSeekEnd - this.lastSeekEnd_);
  15006. }
  15007. this.pastSeekEnd_ = 0;
  15008. this.lastSeekEnd_ = newSeekEnd;
  15009. this.trigger('seekableendchange');
  15010. }
  15011. this.pastSeekEnd_ = this.pastSeekEnd() + 0.03;
  15012. if (this.isBehind_() !== this.behindLiveEdge()) {
  15013. this.behindLiveEdge_ = this.isBehind_();
  15014. this.trigger('liveedgechange');
  15015. }
  15016. }
  15017. /**
  15018. * handle a durationchange event on the player
  15019. * and start/stop tracking accordingly.
  15020. */
  15021. ;
  15022. _proto.handleDurationchange = function handleDurationchange() {
  15023. if (this.player_.duration() === Infinity) {
  15024. this.startTracking();
  15025. } else {
  15026. this.stopTracking();
  15027. }
  15028. }
  15029. /**
  15030. * start tracking live playback
  15031. */
  15032. ;
  15033. _proto.startTracking = function startTracking() {
  15034. var _this2 = this;
  15035. if (this.isTracking()) {
  15036. return;
  15037. }
  15038. this.trackingInterval_ = this.setInterval(this.trackLive_, 30);
  15039. this.trackLive_();
  15040. this.on(this.player_, 'play', this.trackLive_);
  15041. this.on(this.player_, 'pause', this.trackLive_);
  15042. this.one(this.player_, 'play', this.handlePlay); // this is to prevent showing that we are not live
  15043. // before a video starts to play
  15044. if (!this.timeupdateSeen_) {
  15045. this.handleTimeupdate = function () {
  15046. _this2.timeupdateSeen_ = true;
  15047. _this2.handleTimeupdate = null;
  15048. };
  15049. this.one(this.player_, 'timeupdate', this.handleTimeupdate);
  15050. }
  15051. };
  15052. _proto.handlePlay = function handlePlay() {
  15053. this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
  15054. }
  15055. /**
  15056. * Stop tracking, and set all internal variables to
  15057. * their initial value.
  15058. */
  15059. ;
  15060. _proto.reset_ = function reset_() {
  15061. this.pastSeekEnd_ = 0;
  15062. this.lastSeekEnd_ = null;
  15063. this.behindLiveEdge_ = null;
  15064. this.timeupdateSeen_ = false;
  15065. this.clearInterval(this.trackingInterval_);
  15066. this.trackingInterval_ = null;
  15067. this.seekableIncrement_ = 12;
  15068. this.off(this.player_, 'play', this.trackLive_);
  15069. this.off(this.player_, 'pause', this.trackLive_);
  15070. this.off(this.player_, 'play', this.handlePlay);
  15071. this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
  15072. if (this.handleTimeupdate) {
  15073. this.off(this.player_, 'timeupdate', this.handleTimeupdate);
  15074. this.handleTimeupdate = null;
  15075. }
  15076. }
  15077. /**
  15078. * stop tracking live playback
  15079. */
  15080. ;
  15081. _proto.stopTracking = function stopTracking() {
  15082. if (!this.isTracking()) {
  15083. return;
  15084. }
  15085. this.reset_();
  15086. }
  15087. /**
  15088. * A helper to get the player seekable end
  15089. * so that we don't have to null check everywhere
  15090. */
  15091. ;
  15092. _proto.seekableEnd = function seekableEnd() {
  15093. var seekable = this.player_.seekable();
  15094. var seekableEnds = [];
  15095. var i = seekable ? seekable.length : 0;
  15096. while (i--) {
  15097. seekableEnds.push(seekable.end(i));
  15098. } // grab the furthest seekable end after sorting, or if there are none
  15099. // default to Infinity
  15100. return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
  15101. }
  15102. /**
  15103. * A helper to get the player seekable start
  15104. * so that we don't have to null check everywhere
  15105. */
  15106. ;
  15107. _proto.seekableStart = function seekableStart() {
  15108. var seekable = this.player_.seekable();
  15109. var seekableStarts = [];
  15110. var i = seekable ? seekable.length : 0;
  15111. while (i--) {
  15112. seekableStarts.push(seekable.start(i));
  15113. } // grab the first seekable start after sorting, or if there are none
  15114. // default to 0
  15115. return seekableStarts.length ? seekableStarts.sort()[0] : 0;
  15116. }
  15117. /**
  15118. * Get the live time window
  15119. */
  15120. ;
  15121. _proto.liveWindow = function liveWindow() {
  15122. var liveCurrentTime = this.liveCurrentTime();
  15123. if (liveCurrentTime === Infinity) {
  15124. return Infinity;
  15125. }
  15126. return liveCurrentTime - this.seekableStart();
  15127. }
  15128. /**
  15129. * Determines if the player is live, only checks if this component
  15130. * is tracking live playback or not
  15131. */
  15132. ;
  15133. _proto.isLive = function isLive() {
  15134. return this.isTracking();
  15135. }
  15136. /**
  15137. * Determines if currentTime is at the live edge and won't fall behind
  15138. * on each seekableendchange
  15139. */
  15140. ;
  15141. _proto.atLiveEdge = function atLiveEdge() {
  15142. return !this.behindLiveEdge();
  15143. }
  15144. /**
  15145. * get what we expect the live current time to be
  15146. */
  15147. ;
  15148. _proto.liveCurrentTime = function liveCurrentTime() {
  15149. return this.pastSeekEnd() + this.seekableEnd();
  15150. }
  15151. /**
  15152. * Returns how far past seek end we expect current time to be
  15153. */
  15154. ;
  15155. _proto.pastSeekEnd = function pastSeekEnd() {
  15156. return this.pastSeekEnd_;
  15157. }
  15158. /**
  15159. * If we are currently behind the live edge, aka currentTime will be
  15160. * behind on a seekableendchange
  15161. */
  15162. ;
  15163. _proto.behindLiveEdge = function behindLiveEdge() {
  15164. return this.behindLiveEdge_;
  15165. };
  15166. _proto.isTracking = function isTracking() {
  15167. return typeof this.trackingInterval_ === 'number';
  15168. }
  15169. /**
  15170. * Seek to the live edge if we are behind the live edge
  15171. */
  15172. ;
  15173. _proto.seekToLiveEdge = function seekToLiveEdge() {
  15174. if (this.atLiveEdge()) {
  15175. return;
  15176. }
  15177. this.player_.currentTime(this.liveCurrentTime());
  15178. if (this.player_.paused()) {
  15179. this.player_.play();
  15180. }
  15181. };
  15182. _proto.dispose = function dispose() {
  15183. this.stopTracking();
  15184. _Component.prototype.dispose.call(this);
  15185. };
  15186. return LiveTracker;
  15187. }(Component);
  15188. Component.registerComponent('LiveTracker', LiveTracker);
  15189. /**
  15190. * This function is used to fire a sourceset when there is something
  15191. * similar to `mediaEl.load()` being called. It will try to find the source via
  15192. * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
  15193. * with the source that was found or empty string if we cannot know. If it cannot
  15194. * find a source then `sourceset` will not be fired.
  15195. *
  15196. * @param {Html5} tech
  15197. * The tech object that sourceset was setup on
  15198. *
  15199. * @return {boolean}
  15200. * returns false if the sourceset was not fired and true otherwise.
  15201. */
  15202. var sourcesetLoad = function sourcesetLoad(tech) {
  15203. var el = tech.el(); // if `el.src` is set, that source will be loaded.
  15204. if (el.hasAttribute('src')) {
  15205. tech.triggerSourceset(el.src);
  15206. return true;
  15207. }
  15208. /**
  15209. * Since there isn't a src property on the media element, source elements will be used for
  15210. * implementing the source selection algorithm. This happens asynchronously and
  15211. * for most cases were there is more than one source we cannot tell what source will
  15212. * be loaded, without re-implementing the source selection algorithm. At this time we are not
  15213. * going to do that. There are three special cases that we do handle here though:
  15214. *
  15215. * 1. If there are no sources, do not fire `sourceset`.
  15216. * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
  15217. * 3. If there is more than one `<source>` but all of them have the same `src` url.
  15218. * That will be our src.
  15219. */
  15220. var sources = tech.$$('source');
  15221. var srcUrls = [];
  15222. var src = ''; // if there are no sources, do not fire sourceset
  15223. if (!sources.length) {
  15224. return false;
  15225. } // only count valid/non-duplicate source elements
  15226. for (var i = 0; i < sources.length; i++) {
  15227. var url = sources[i].src;
  15228. if (url && srcUrls.indexOf(url) === -1) {
  15229. srcUrls.push(url);
  15230. }
  15231. } // there were no valid sources
  15232. if (!srcUrls.length) {
  15233. return false;
  15234. } // there is only one valid source element url
  15235. // use that
  15236. if (srcUrls.length === 1) {
  15237. src = srcUrls[0];
  15238. }
  15239. tech.triggerSourceset(src);
  15240. return true;
  15241. };
  15242. /**
  15243. * our implementation of an `innerHTML` descriptor for browsers
  15244. * that do not have one.
  15245. */
  15246. var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  15247. get: function get() {
  15248. return this.cloneNode(true).innerHTML;
  15249. },
  15250. set: function set(v) {
  15251. // make a dummy node to use innerHTML on
  15252. var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
  15253. dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
  15254. var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
  15255. // to the document fragment
  15256. while (dummy.childNodes.length) {
  15257. docFrag.appendChild(dummy.childNodes[0]);
  15258. } // remove content
  15259. this.innerText = ''; // now we add all of that html in one by appending the
  15260. // document fragment. This is how innerHTML does it.
  15261. window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
  15262. return this.innerHTML;
  15263. }
  15264. });
  15265. /**
  15266. * Get a property descriptor given a list of priorities and the
  15267. * property to get.
  15268. */
  15269. var getDescriptor = function getDescriptor(priority, prop) {
  15270. var descriptor = {};
  15271. for (var i = 0; i < priority.length; i++) {
  15272. descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
  15273. if (descriptor && descriptor.set && descriptor.get) {
  15274. break;
  15275. }
  15276. }
  15277. descriptor.enumerable = true;
  15278. descriptor.configurable = true;
  15279. return descriptor;
  15280. };
  15281. var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
  15282. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
  15283. };
  15284. /**
  15285. * Patches browser internal functions so that we can tell synchronously
  15286. * if a `<source>` was appended to the media element. For some reason this
  15287. * causes a `sourceset` if the the media element is ready and has no source.
  15288. * This happens when:
  15289. * - The page has just loaded and the media element does not have a source.
  15290. * - The media element was emptied of all sources, then `load()` was called.
  15291. *
  15292. * It does this by patching the following functions/properties when they are supported:
  15293. *
  15294. * - `append()` - can be used to add a `<source>` element to the media element
  15295. * - `appendChild()` - can be used to add a `<source>` element to the media element
  15296. * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
  15297. * - `innerHTML` - can be used to add a `<source>` element to the media element
  15298. *
  15299. * @param {Html5} tech
  15300. * The tech object that sourceset is being setup on.
  15301. */
  15302. var firstSourceWatch = function firstSourceWatch(tech) {
  15303. var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
  15304. if (el.resetSourceWatch_) {
  15305. return;
  15306. }
  15307. var old = {};
  15308. var innerDescriptor = getInnerHTMLDescriptor(tech);
  15309. var appendWrapper = function appendWrapper(appendFn) {
  15310. return function () {
  15311. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  15312. args[_key] = arguments[_key];
  15313. }
  15314. var retval = appendFn.apply(el, args);
  15315. sourcesetLoad(tech);
  15316. return retval;
  15317. };
  15318. };
  15319. ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
  15320. if (!el[k]) {
  15321. return;
  15322. } // store the old function
  15323. old[k] = el[k]; // call the old function with a sourceset if a source
  15324. // was loaded
  15325. el[k] = appendWrapper(old[k]);
  15326. });
  15327. Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
  15328. set: appendWrapper(innerDescriptor.set)
  15329. }));
  15330. el.resetSourceWatch_ = function () {
  15331. el.resetSourceWatch_ = null;
  15332. Object.keys(old).forEach(function (k) {
  15333. el[k] = old[k];
  15334. });
  15335. Object.defineProperty(el, 'innerHTML', innerDescriptor);
  15336. }; // on the first sourceset, we need to revert our changes
  15337. tech.one('sourceset', el.resetSourceWatch_);
  15338. };
  15339. /**
  15340. * our implementation of a `src` descriptor for browsers
  15341. * that do not have one.
  15342. */
  15343. var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  15344. get: function get() {
  15345. if (this.hasAttribute('src')) {
  15346. return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
  15347. }
  15348. return '';
  15349. },
  15350. set: function set(v) {
  15351. window$1.Element.prototype.setAttribute.call(this, 'src', v);
  15352. return v;
  15353. }
  15354. });
  15355. var getSrcDescriptor = function getSrcDescriptor(tech) {
  15356. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
  15357. };
  15358. /**
  15359. * setup `sourceset` handling on the `Html5` tech. This function
  15360. * patches the following element properties/functions:
  15361. *
  15362. * - `src` - to determine when `src` is set
  15363. * - `setAttribute()` - to determine when `src` is set
  15364. * - `load()` - this re-triggers the source selection algorithm, and can
  15365. * cause a sourceset.
  15366. *
  15367. * If there is no source when we are adding `sourceset` support or during a `load()`
  15368. * we also patch the functions listed in `firstSourceWatch`.
  15369. *
  15370. * @param {Html5} tech
  15371. * The tech to patch
  15372. */
  15373. var setupSourceset = function setupSourceset(tech) {
  15374. if (!tech.featuresSourceset) {
  15375. return;
  15376. }
  15377. var el = tech.el(); // make sure sourceset isn't setup twice.
  15378. if (el.resetSourceset_) {
  15379. return;
  15380. }
  15381. var srcDescriptor = getSrcDescriptor(tech);
  15382. var oldSetAttribute = el.setAttribute;
  15383. var oldLoad = el.load;
  15384. Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
  15385. set: function set(v) {
  15386. var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
  15387. tech.triggerSourceset(el.src);
  15388. return retval;
  15389. }
  15390. }));
  15391. el.setAttribute = function (n, v) {
  15392. var retval = oldSetAttribute.call(el, n, v);
  15393. if (/src/i.test(n)) {
  15394. tech.triggerSourceset(el.src);
  15395. }
  15396. return retval;
  15397. };
  15398. el.load = function () {
  15399. var retval = oldLoad.call(el); // if load was called, but there was no source to fire
  15400. // sourceset on. We have to watch for a source append
  15401. // as that can trigger a `sourceset` when the media element
  15402. // has no source
  15403. if (!sourcesetLoad(tech)) {
  15404. tech.triggerSourceset('');
  15405. firstSourceWatch(tech);
  15406. }
  15407. return retval;
  15408. };
  15409. if (el.currentSrc) {
  15410. tech.triggerSourceset(el.currentSrc);
  15411. } else if (!sourcesetLoad(tech)) {
  15412. firstSourceWatch(tech);
  15413. }
  15414. el.resetSourceset_ = function () {
  15415. el.resetSourceset_ = null;
  15416. el.load = oldLoad;
  15417. el.setAttribute = oldSetAttribute;
  15418. Object.defineProperty(el, 'src', srcDescriptor);
  15419. if (el.resetSourceWatch_) {
  15420. el.resetSourceWatch_();
  15421. }
  15422. };
  15423. };
  15424. function _templateObject$1() {
  15425. 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."]);
  15426. _templateObject$1 = function _templateObject() {
  15427. return data;
  15428. };
  15429. return data;
  15430. }
  15431. /**
  15432. * HTML5 Media Controller - Wrapper for HTML5 Media API
  15433. *
  15434. * @mixes Tech~SourceHandlerAdditions
  15435. * @extends Tech
  15436. */
  15437. var Html5 =
  15438. /*#__PURE__*/
  15439. function (_Tech) {
  15440. _inheritsLoose(Html5, _Tech);
  15441. /**
  15442. * Create an instance of this Tech.
  15443. *
  15444. * @param {Object} [options]
  15445. * The key/value store of player options.
  15446. *
  15447. * @param {Component~ReadyCallback} ready
  15448. * Callback function to call when the `HTML5` Tech is ready.
  15449. */
  15450. function Html5(options, ready) {
  15451. var _this;
  15452. _this = _Tech.call(this, options, ready) || this;
  15453. var source = options.source;
  15454. var crossoriginTracks = false; // Set the source if one is provided
  15455. // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
  15456. // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
  15457. // anyway so the error gets fired.
  15458. if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
  15459. _this.setSource(source);
  15460. } else {
  15461. _this.handleLateInit_(_this.el_);
  15462. } // setup sourceset after late sourceset/init
  15463. if (options.enableSourceset) {
  15464. _this.setupSourcesetHandling_();
  15465. }
  15466. if (_this.el_.hasChildNodes()) {
  15467. var nodes = _this.el_.childNodes;
  15468. var nodesLength = nodes.length;
  15469. var removeNodes = [];
  15470. while (nodesLength--) {
  15471. var node = nodes[nodesLength];
  15472. var nodeName = node.nodeName.toLowerCase();
  15473. if (nodeName === 'track') {
  15474. if (!_this.featuresNativeTextTracks) {
  15475. // Empty video tag tracks so the built-in player doesn't use them also.
  15476. // This may not be fast enough to stop HTML5 browsers from reading the tags
  15477. // so we'll need to turn off any default tracks if we're manually doing
  15478. // captions and subtitles. videoElement.textTracks
  15479. removeNodes.push(node);
  15480. } else {
  15481. // store HTMLTrackElement and TextTrack to remote list
  15482. _this.remoteTextTrackEls().addTrackElement_(node);
  15483. _this.remoteTextTracks().addTrack(node.track);
  15484. _this.textTracks().addTrack(node.track);
  15485. if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
  15486. crossoriginTracks = true;
  15487. }
  15488. }
  15489. }
  15490. }
  15491. for (var i = 0; i < removeNodes.length; i++) {
  15492. _this.el_.removeChild(removeNodes[i]);
  15493. }
  15494. }
  15495. _this.proxyNativeTracks_();
  15496. if (_this.featuresNativeTextTracks && crossoriginTracks) {
  15497. log.warn(tsml(_templateObject$1()));
  15498. } // prevent iOS Safari from disabling metadata text tracks during native playback
  15499. _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
  15500. // Our goal should be to get the custom controls on mobile solid everywhere
  15501. // so we can remove this all together. Right now this will block custom
  15502. // controls on touch enabled laptops like the Chrome Pixel
  15503. if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
  15504. _this.setControls(true);
  15505. } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
  15506. // into a `fullscreenchange` event
  15507. _this.proxyWebkitFullscreen_();
  15508. _this.triggerReady();
  15509. return _this;
  15510. }
  15511. /**
  15512. * Dispose of `HTML5` media element and remove all tracks.
  15513. */
  15514. var _proto = Html5.prototype;
  15515. _proto.dispose = function dispose() {
  15516. if (this.el_ && this.el_.resetSourceset_) {
  15517. this.el_.resetSourceset_();
  15518. }
  15519. Html5.disposeMediaElement(this.el_);
  15520. this.options_ = null; // tech will handle clearing of the emulated track list
  15521. _Tech.prototype.dispose.call(this);
  15522. }
  15523. /**
  15524. * Modify the media element so that we can detect when
  15525. * the source is changed. Fires `sourceset` just after the source has changed
  15526. */
  15527. ;
  15528. _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
  15529. setupSourceset(this);
  15530. }
  15531. /**
  15532. * When a captions track is enabled in the iOS Safari native player, all other
  15533. * tracks are disabled (including metadata tracks), which nulls all of their
  15534. * associated cue points. This will restore metadata tracks to their pre-fullscreen
  15535. * state in those cases so that cue points are not needlessly lost.
  15536. *
  15537. * @private
  15538. */
  15539. ;
  15540. _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
  15541. var textTracks = this.textTracks();
  15542. var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
  15543. var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
  15544. metadataTracksPreFullscreenState = [];
  15545. for (var i = 0; i < textTracks.length; i++) {
  15546. var track = textTracks[i];
  15547. if (track.kind === 'metadata') {
  15548. metadataTracksPreFullscreenState.push({
  15549. track: track,
  15550. storedMode: track.mode
  15551. });
  15552. }
  15553. }
  15554. }; // snapshot each metadata track's initial state, and update the snapshot
  15555. // each time there is a track 'change' event
  15556. takeMetadataTrackSnapshot();
  15557. textTracks.addEventListener('change', takeMetadataTrackSnapshot);
  15558. this.on('dispose', function () {
  15559. return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  15560. });
  15561. var restoreTrackMode = function restoreTrackMode() {
  15562. for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
  15563. var storedTrack = metadataTracksPreFullscreenState[i];
  15564. if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
  15565. storedTrack.track.mode = storedTrack.storedMode;
  15566. }
  15567. } // we only want this handler to be executed on the first 'change' event
  15568. textTracks.removeEventListener('change', restoreTrackMode);
  15569. }; // when we enter fullscreen playback, stop updating the snapshot and
  15570. // restore all track modes to their pre-fullscreen state
  15571. this.on('webkitbeginfullscreen', function () {
  15572. textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
  15573. textTracks.removeEventListener('change', restoreTrackMode);
  15574. textTracks.addEventListener('change', restoreTrackMode);
  15575. }); // start updating the snapshot again after leaving fullscreen
  15576. this.on('webkitendfullscreen', function () {
  15577. // remove the listener before adding it just in case it wasn't previously removed
  15578. textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  15579. textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
  15580. textTracks.removeEventListener('change', restoreTrackMode);
  15581. });
  15582. }
  15583. /**
  15584. * Attempt to force override of tracks for the given type
  15585. *
  15586. * @param {string} type - Track type to override, possible values include 'Audio',
  15587. * 'Video', and 'Text'.
  15588. * @param {boolean} override - If set to true native audio/video will be overridden,
  15589. * otherwise native audio/video will potentially be used.
  15590. * @private
  15591. */
  15592. ;
  15593. _proto.overrideNative_ = function overrideNative_(type, override) {
  15594. var _this2 = this;
  15595. // If there is no behavioral change don't add/remove listeners
  15596. if (override !== this["featuresNative" + type + "Tracks"]) {
  15597. return;
  15598. }
  15599. var lowerCaseType = type.toLowerCase();
  15600. if (this[lowerCaseType + "TracksListeners_"]) {
  15601. Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) {
  15602. var elTracks = _this2.el()[lowerCaseType + "Tracks"];
  15603. elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
  15604. });
  15605. }
  15606. this["featuresNative" + type + "Tracks"] = !override;
  15607. this[lowerCaseType + "TracksListeners_"] = null;
  15608. this.proxyNativeTracksForType_(lowerCaseType);
  15609. }
  15610. /**
  15611. * Attempt to force override of native audio tracks.
  15612. *
  15613. * @param {boolean} override - If set to true native audio will be overridden,
  15614. * otherwise native audio will potentially be used.
  15615. */
  15616. ;
  15617. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
  15618. this.overrideNative_('Audio', override);
  15619. }
  15620. /**
  15621. * Attempt to force override of native video tracks.
  15622. *
  15623. * @param {boolean} override - If set to true native video will be overridden,
  15624. * otherwise native video will potentially be used.
  15625. */
  15626. ;
  15627. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
  15628. this.overrideNative_('Video', override);
  15629. }
  15630. /**
  15631. * Proxy native track list events for the given type to our track
  15632. * lists if the browser we are playing in supports that type of track list.
  15633. *
  15634. * @param {string} name - Track type; values include 'audio', 'video', and 'text'
  15635. * @private
  15636. */
  15637. ;
  15638. _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
  15639. var _this3 = this;
  15640. var props = NORMAL[name];
  15641. var elTracks = this.el()[props.getterName];
  15642. var techTracks = this[props.getterName]();
  15643. if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
  15644. return;
  15645. }
  15646. var listeners = {
  15647. change: function change(e) {
  15648. techTracks.trigger({
  15649. type: 'change',
  15650. target: techTracks,
  15651. currentTarget: techTracks,
  15652. srcElement: techTracks
  15653. });
  15654. },
  15655. addtrack: function addtrack(e) {
  15656. techTracks.addTrack(e.track);
  15657. },
  15658. removetrack: function removetrack(e) {
  15659. techTracks.removeTrack(e.track);
  15660. }
  15661. };
  15662. var removeOldTracks = function removeOldTracks() {
  15663. var removeTracks = [];
  15664. for (var i = 0; i < techTracks.length; i++) {
  15665. var found = false;
  15666. for (var j = 0; j < elTracks.length; j++) {
  15667. if (elTracks[j] === techTracks[i]) {
  15668. found = true;
  15669. break;
  15670. }
  15671. }
  15672. if (!found) {
  15673. removeTracks.push(techTracks[i]);
  15674. }
  15675. }
  15676. while (removeTracks.length) {
  15677. techTracks.removeTrack(removeTracks.shift());
  15678. }
  15679. };
  15680. this[props.getterName + 'Listeners_'] = listeners;
  15681. Object.keys(listeners).forEach(function (eventName) {
  15682. var listener = listeners[eventName];
  15683. elTracks.addEventListener(eventName, listener);
  15684. _this3.on('dispose', function (e) {
  15685. return elTracks.removeEventListener(eventName, listener);
  15686. });
  15687. }); // Remove (native) tracks that are not used anymore
  15688. this.on('loadstart', removeOldTracks);
  15689. this.on('dispose', function (e) {
  15690. return _this3.off('loadstart', removeOldTracks);
  15691. });
  15692. }
  15693. /**
  15694. * Proxy all native track list events to our track lists if the browser we are playing
  15695. * in supports that type of track list.
  15696. *
  15697. * @private
  15698. */
  15699. ;
  15700. _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
  15701. var _this4 = this;
  15702. NORMAL.names.forEach(function (name) {
  15703. _this4.proxyNativeTracksForType_(name);
  15704. });
  15705. }
  15706. /**
  15707. * Create the `Html5` Tech's DOM element.
  15708. *
  15709. * @return {Element}
  15710. * The element that gets created.
  15711. */
  15712. ;
  15713. _proto.createEl = function createEl$$1() {
  15714. var el = this.options_.tag; // Check if this browser supports moving the element into the box.
  15715. // On the iPhone video will break if you move the element,
  15716. // So we have to create a brand new element.
  15717. // If we ingested the player div, we do not need to move the media element.
  15718. if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
  15719. // If the original tag is still there, clone and remove it.
  15720. if (el) {
  15721. var clone = el.cloneNode(true);
  15722. if (el.parentNode) {
  15723. el.parentNode.insertBefore(clone, el);
  15724. }
  15725. Html5.disposeMediaElement(el);
  15726. el = clone;
  15727. } else {
  15728. el = document.createElement('video'); // determine if native controls should be used
  15729. var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
  15730. var attributes = mergeOptions({}, tagAttributes);
  15731. if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
  15732. delete attributes.controls;
  15733. }
  15734. setAttributes(el, assign(attributes, {
  15735. id: this.options_.techId,
  15736. class: 'vjs-tech'
  15737. }));
  15738. }
  15739. el.playerId = this.options_.playerId;
  15740. }
  15741. if (typeof this.options_.preload !== 'undefined') {
  15742. setAttribute(el, 'preload', this.options_.preload);
  15743. } // Update specific tag settings, in case they were overridden
  15744. // `autoplay` has to be *last* so that `muted` and `playsinline` are present
  15745. // when iOS/Safari or other browsers attempt to autoplay.
  15746. var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
  15747. for (var i = 0; i < settingsAttrs.length; i++) {
  15748. var attr = settingsAttrs[i];
  15749. var value = this.options_[attr];
  15750. if (typeof value !== 'undefined') {
  15751. if (value) {
  15752. setAttribute(el, attr, attr);
  15753. } else {
  15754. removeAttribute(el, attr);
  15755. }
  15756. el[attr] = value;
  15757. }
  15758. }
  15759. return el;
  15760. }
  15761. /**
  15762. * This will be triggered if the loadstart event has already fired, before videojs was
  15763. * ready. Two known examples of when this can happen are:
  15764. * 1. If we're loading the playback object after it has started loading
  15765. * 2. The media is already playing the (often with autoplay on) then
  15766. *
  15767. * This function will fire another loadstart so that videojs can catchup.
  15768. *
  15769. * @fires Tech#loadstart
  15770. *
  15771. * @return {undefined}
  15772. * returns nothing.
  15773. */
  15774. ;
  15775. _proto.handleLateInit_ = function handleLateInit_(el) {
  15776. if (el.networkState === 0 || el.networkState === 3) {
  15777. // The video element hasn't started loading the source yet
  15778. // or didn't find a source
  15779. return;
  15780. }
  15781. if (el.readyState === 0) {
  15782. // NetworkState is set synchronously BUT loadstart is fired at the
  15783. // end of the current stack, usually before setInterval(fn, 0).
  15784. // So at this point we know loadstart may have already fired or is
  15785. // about to fire, and either way the player hasn't seen it yet.
  15786. // We don't want to fire loadstart prematurely here and cause a
  15787. // double loadstart so we'll wait and see if it happens between now
  15788. // and the next loop, and fire it if not.
  15789. // HOWEVER, we also want to make sure it fires before loadedmetadata
  15790. // which could also happen between now and the next loop, so we'll
  15791. // watch for that also.
  15792. var loadstartFired = false;
  15793. var setLoadstartFired = function setLoadstartFired() {
  15794. loadstartFired = true;
  15795. };
  15796. this.on('loadstart', setLoadstartFired);
  15797. var triggerLoadstart = function triggerLoadstart() {
  15798. // We did miss the original loadstart. Make sure the player
  15799. // sees loadstart before loadedmetadata
  15800. if (!loadstartFired) {
  15801. this.trigger('loadstart');
  15802. }
  15803. };
  15804. this.on('loadedmetadata', triggerLoadstart);
  15805. this.ready(function () {
  15806. this.off('loadstart', setLoadstartFired);
  15807. this.off('loadedmetadata', triggerLoadstart);
  15808. if (!loadstartFired) {
  15809. // We did miss the original native loadstart. Fire it now.
  15810. this.trigger('loadstart');
  15811. }
  15812. });
  15813. return;
  15814. } // From here on we know that loadstart already fired and we missed it.
  15815. // The other readyState events aren't as much of a problem if we double
  15816. // them, so not going to go to as much trouble as loadstart to prevent
  15817. // that unless we find reason to.
  15818. var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
  15819. eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
  15820. if (el.readyState >= 2) {
  15821. eventsToTrigger.push('loadeddata');
  15822. } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
  15823. if (el.readyState >= 3) {
  15824. eventsToTrigger.push('canplay');
  15825. } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
  15826. if (el.readyState >= 4) {
  15827. eventsToTrigger.push('canplaythrough');
  15828. } // We still need to give the player time to add event listeners
  15829. this.ready(function () {
  15830. eventsToTrigger.forEach(function (type) {
  15831. this.trigger(type);
  15832. }, this);
  15833. });
  15834. }
  15835. /**
  15836. * Set current time for the `HTML5` tech.
  15837. *
  15838. * @param {number} seconds
  15839. * Set the current time of the media to this.
  15840. */
  15841. ;
  15842. _proto.setCurrentTime = function setCurrentTime(seconds) {
  15843. try {
  15844. this.el_.currentTime = seconds;
  15845. } catch (e) {
  15846. log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
  15847. }
  15848. }
  15849. /**
  15850. * Get the current duration of the HTML5 media element.
  15851. *
  15852. * @return {number}
  15853. * The duration of the media or 0 if there is no duration.
  15854. */
  15855. ;
  15856. _proto.duration = function duration() {
  15857. var _this5 = this;
  15858. // Android Chrome will report duration as Infinity for VOD HLS until after
  15859. // playback has started, which triggers the live display erroneously.
  15860. // Return NaN if playback has not started and trigger a durationupdate once
  15861. // the duration can be reliably known.
  15862. if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
  15863. // Wait for the first `timeupdate` with currentTime > 0 - there may be
  15864. // several with 0
  15865. var checkProgress = function checkProgress() {
  15866. if (_this5.el_.currentTime > 0) {
  15867. // Trigger durationchange for genuinely live video
  15868. if (_this5.el_.duration === Infinity) {
  15869. _this5.trigger('durationchange');
  15870. }
  15871. _this5.off('timeupdate', checkProgress);
  15872. }
  15873. };
  15874. this.on('timeupdate', checkProgress);
  15875. return NaN;
  15876. }
  15877. return this.el_.duration || NaN;
  15878. }
  15879. /**
  15880. * Get the current width of the HTML5 media element.
  15881. *
  15882. * @return {number}
  15883. * The width of the HTML5 media element.
  15884. */
  15885. ;
  15886. _proto.width = function width() {
  15887. return this.el_.offsetWidth;
  15888. }
  15889. /**
  15890. * Get the current height of the HTML5 media element.
  15891. *
  15892. * @return {number}
  15893. * The height of the HTML5 media element.
  15894. */
  15895. ;
  15896. _proto.height = function height() {
  15897. return this.el_.offsetHeight;
  15898. }
  15899. /**
  15900. * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
  15901. * `fullscreenchange` event.
  15902. *
  15903. * @private
  15904. * @fires fullscreenchange
  15905. * @listens webkitendfullscreen
  15906. * @listens webkitbeginfullscreen
  15907. * @listens webkitbeginfullscreen
  15908. */
  15909. ;
  15910. _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
  15911. var _this6 = this;
  15912. if (!('webkitDisplayingFullscreen' in this.el_)) {
  15913. return;
  15914. }
  15915. var endFn = function endFn() {
  15916. this.trigger('fullscreenchange', {
  15917. isFullscreen: false
  15918. });
  15919. };
  15920. var beginFn = function beginFn() {
  15921. if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
  15922. this.one('webkitendfullscreen', endFn);
  15923. this.trigger('fullscreenchange', {
  15924. isFullscreen: true
  15925. });
  15926. }
  15927. };
  15928. this.on('webkitbeginfullscreen', beginFn);
  15929. this.on('dispose', function () {
  15930. _this6.off('webkitbeginfullscreen', beginFn);
  15931. _this6.off('webkitendfullscreen', endFn);
  15932. });
  15933. }
  15934. /**
  15935. * Check if fullscreen is supported on the current playback device.
  15936. *
  15937. * @return {boolean}
  15938. * - True if fullscreen is supported.
  15939. * - False if fullscreen is not supported.
  15940. */
  15941. ;
  15942. _proto.supportsFullScreen = function supportsFullScreen() {
  15943. if (typeof this.el_.webkitEnterFullScreen === 'function') {
  15944. var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
  15945. if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
  15946. return true;
  15947. }
  15948. }
  15949. return false;
  15950. }
  15951. /**
  15952. * Request that the `HTML5` Tech enter fullscreen.
  15953. */
  15954. ;
  15955. _proto.enterFullScreen = function enterFullScreen() {
  15956. var video = this.el_;
  15957. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  15958. // attempt to prime the video element for programmatic access
  15959. // this isn't necessary on the desktop but shouldn't hurt
  15960. this.el_.play(); // playing and pausing synchronously during the transition to fullscreen
  15961. // can get iOS ~6.1 devices into a play/pause loop
  15962. this.setTimeout(function () {
  15963. video.pause();
  15964. video.webkitEnterFullScreen();
  15965. }, 0);
  15966. } else {
  15967. video.webkitEnterFullScreen();
  15968. }
  15969. }
  15970. /**
  15971. * Request that the `HTML5` Tech exit fullscreen.
  15972. */
  15973. ;
  15974. _proto.exitFullScreen = function exitFullScreen() {
  15975. this.el_.webkitExitFullScreen();
  15976. }
  15977. /**
  15978. * A getter/setter for the `Html5` Tech's source object.
  15979. * > Note: Please use {@link Html5#setSource}
  15980. *
  15981. * @param {Tech~SourceObject} [src]
  15982. * The source object you want to set on the `HTML5` techs element.
  15983. *
  15984. * @return {Tech~SourceObject|undefined}
  15985. * - The current source object when a source is not passed in.
  15986. * - undefined when setting
  15987. *
  15988. * @deprecated Since version 5.
  15989. */
  15990. ;
  15991. _proto.src = function src(_src) {
  15992. if (_src === undefined) {
  15993. return this.el_.src;
  15994. } // Setting src through `src` instead of `setSrc` will be deprecated
  15995. this.setSrc(_src);
  15996. }
  15997. /**
  15998. * Reset the tech by removing all sources and then calling
  15999. * {@link Html5.resetMediaElement}.
  16000. */
  16001. ;
  16002. _proto.reset = function reset() {
  16003. Html5.resetMediaElement(this.el_);
  16004. }
  16005. /**
  16006. * Get the current source on the `HTML5` Tech. Falls back to returning the source from
  16007. * the HTML5 media element.
  16008. *
  16009. * @return {Tech~SourceObject}
  16010. * The current source object from the HTML5 tech. With a fallback to the
  16011. * elements source.
  16012. */
  16013. ;
  16014. _proto.currentSrc = function currentSrc() {
  16015. if (this.currentSource_) {
  16016. return this.currentSource_.src;
  16017. }
  16018. return this.el_.currentSrc;
  16019. }
  16020. /**
  16021. * Set controls attribute for the HTML5 media Element.
  16022. *
  16023. * @param {string} val
  16024. * Value to set the controls attribute to
  16025. */
  16026. ;
  16027. _proto.setControls = function setControls(val) {
  16028. this.el_.controls = !!val;
  16029. }
  16030. /**
  16031. * Create and returns a remote {@link TextTrack} object.
  16032. *
  16033. * @param {string} kind
  16034. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  16035. *
  16036. * @param {string} [label]
  16037. * Label to identify the text track
  16038. *
  16039. * @param {string} [language]
  16040. * Two letter language abbreviation
  16041. *
  16042. * @return {TextTrack}
  16043. * The TextTrack that gets created.
  16044. */
  16045. ;
  16046. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  16047. if (!this.featuresNativeTextTracks) {
  16048. return _Tech.prototype.addTextTrack.call(this, kind, label, language);
  16049. }
  16050. return this.el_.addTextTrack(kind, label, language);
  16051. }
  16052. /**
  16053. * Creates either native TextTrack or an emulated TextTrack depending
  16054. * on the value of `featuresNativeTextTracks`
  16055. *
  16056. * @param {Object} options
  16057. * The object should contain the options to initialize the TextTrack with.
  16058. *
  16059. * @param {string} [options.kind]
  16060. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  16061. *
  16062. * @param {string} [options.label]
  16063. * Label to identify the text track
  16064. *
  16065. * @param {string} [options.language]
  16066. * Two letter language abbreviation.
  16067. *
  16068. * @param {boolean} [options.default]
  16069. * Default this track to on.
  16070. *
  16071. * @param {string} [options.id]
  16072. * The internal id to assign this track.
  16073. *
  16074. * @param {string} [options.src]
  16075. * A source url for the track.
  16076. *
  16077. * @return {HTMLTrackElement}
  16078. * The track element that gets created.
  16079. */
  16080. ;
  16081. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  16082. if (!this.featuresNativeTextTracks) {
  16083. return _Tech.prototype.createRemoteTextTrack.call(this, options);
  16084. }
  16085. var htmlTrackElement = document.createElement('track');
  16086. if (options.kind) {
  16087. htmlTrackElement.kind = options.kind;
  16088. }
  16089. if (options.label) {
  16090. htmlTrackElement.label = options.label;
  16091. }
  16092. if (options.language || options.srclang) {
  16093. htmlTrackElement.srclang = options.language || options.srclang;
  16094. }
  16095. if (options.default) {
  16096. htmlTrackElement.default = options.default;
  16097. }
  16098. if (options.id) {
  16099. htmlTrackElement.id = options.id;
  16100. }
  16101. if (options.src) {
  16102. htmlTrackElement.src = options.src;
  16103. }
  16104. return htmlTrackElement;
  16105. }
  16106. /**
  16107. * Creates a remote text track object and returns an html track element.
  16108. *
  16109. * @param {Object} options The object should contain values for
  16110. * kind, language, label, and src (location of the WebVTT file)
  16111. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  16112. * automatically removed from the video element whenever the source changes
  16113. * @return {HTMLTrackElement} An Html Track Element.
  16114. * This can be an emulated {@link HTMLTrackElement} or a native one.
  16115. * @deprecated The default value of the "manualCleanup" parameter will default
  16116. * to "false" in upcoming versions of Video.js
  16117. */
  16118. ;
  16119. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  16120. var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
  16121. if (this.featuresNativeTextTracks) {
  16122. this.el().appendChild(htmlTrackElement);
  16123. }
  16124. return htmlTrackElement;
  16125. }
  16126. /**
  16127. * Remove remote `TextTrack` from `TextTrackList` object
  16128. *
  16129. * @param {TextTrack} track
  16130. * `TextTrack` object to remove
  16131. */
  16132. ;
  16133. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  16134. _Tech.prototype.removeRemoteTextTrack.call(this, track);
  16135. if (this.featuresNativeTextTracks) {
  16136. var tracks = this.$$('track');
  16137. var i = tracks.length;
  16138. while (i--) {
  16139. if (track === tracks[i] || track === tracks[i].track) {
  16140. this.el().removeChild(tracks[i]);
  16141. }
  16142. }
  16143. }
  16144. }
  16145. /**
  16146. * Gets available media playback quality metrics as specified by the W3C's Media
  16147. * Playback Quality API.
  16148. *
  16149. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  16150. *
  16151. * @return {Object}
  16152. * An object with supported media playback quality metrics
  16153. */
  16154. ;
  16155. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  16156. if (typeof this.el().getVideoPlaybackQuality === 'function') {
  16157. return this.el().getVideoPlaybackQuality();
  16158. }
  16159. var videoPlaybackQuality = {};
  16160. if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
  16161. videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
  16162. videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
  16163. }
  16164. if (window$1.performance && typeof window$1.performance.now === 'function') {
  16165. videoPlaybackQuality.creationTime = window$1.performance.now();
  16166. } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
  16167. videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
  16168. }
  16169. return videoPlaybackQuality;
  16170. };
  16171. return Html5;
  16172. }(Tech);
  16173. /* HTML5 Support Testing ---------------------------------------------------- */
  16174. if (isReal()) {
  16175. /**
  16176. * Element for testing browser HTML5 media capabilities
  16177. *
  16178. * @type {Element}
  16179. * @constant
  16180. * @private
  16181. */
  16182. Html5.TEST_VID = document.createElement('video');
  16183. var track = document.createElement('track');
  16184. track.kind = 'captions';
  16185. track.srclang = 'en';
  16186. track.label = 'English';
  16187. Html5.TEST_VID.appendChild(track);
  16188. }
  16189. /**
  16190. * Check if HTML5 media is supported by this browser/device.
  16191. *
  16192. * @return {boolean}
  16193. * - True if HTML5 media is supported.
  16194. * - False if HTML5 media is not supported.
  16195. */
  16196. Html5.isSupported = function () {
  16197. // IE with no Media Player is a LIAR! (#984)
  16198. try {
  16199. Html5.TEST_VID.volume = 0.5;
  16200. } catch (e) {
  16201. return false;
  16202. }
  16203. return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
  16204. };
  16205. /**
  16206. * Check if the tech can support the given type
  16207. *
  16208. * @param {string} type
  16209. * The mimetype to check
  16210. * @return {string} 'probably', 'maybe', or '' (empty string)
  16211. */
  16212. Html5.canPlayType = function (type) {
  16213. return Html5.TEST_VID.canPlayType(type);
  16214. };
  16215. /**
  16216. * Check if the tech can support the given source
  16217. *
  16218. * @param {Object} srcObj
  16219. * The source object
  16220. * @param {Object} options
  16221. * The options passed to the tech
  16222. * @return {string} 'probably', 'maybe', or '' (empty string)
  16223. */
  16224. Html5.canPlaySource = function (srcObj, options) {
  16225. return Html5.canPlayType(srcObj.type);
  16226. };
  16227. /**
  16228. * Check if the volume can be changed in this browser/device.
  16229. * Volume cannot be changed in a lot of mobile devices.
  16230. * Specifically, it can't be changed from 1 on iOS.
  16231. *
  16232. * @return {boolean}
  16233. * - True if volume can be controlled
  16234. * - False otherwise
  16235. */
  16236. Html5.canControlVolume = function () {
  16237. // IE will error if Windows Media Player not installed #3315
  16238. try {
  16239. var volume = Html5.TEST_VID.volume;
  16240. Html5.TEST_VID.volume = volume / 2 + 0.1;
  16241. return volume !== Html5.TEST_VID.volume;
  16242. } catch (e) {
  16243. return false;
  16244. }
  16245. };
  16246. /**
  16247. * Check if the volume can be muted in this browser/device.
  16248. * Some devices, e.g. iOS, don't allow changing volume
  16249. * but permits muting/unmuting.
  16250. *
  16251. * @return {bolean}
  16252. * - True if volume can be muted
  16253. * - False otherwise
  16254. */
  16255. Html5.canMuteVolume = function () {
  16256. try {
  16257. var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
  16258. // work, so we want to set both property and attribute
  16259. Html5.TEST_VID.muted = !muted;
  16260. if (Html5.TEST_VID.muted) {
  16261. setAttribute(Html5.TEST_VID, 'muted', 'muted');
  16262. } else {
  16263. removeAttribute(Html5.TEST_VID, 'muted', 'muted');
  16264. }
  16265. return muted !== Html5.TEST_VID.muted;
  16266. } catch (e) {
  16267. return false;
  16268. }
  16269. };
  16270. /**
  16271. * Check if the playback rate can be changed in this browser/device.
  16272. *
  16273. * @return {boolean}
  16274. * - True if playback rate can be controlled
  16275. * - False otherwise
  16276. */
  16277. Html5.canControlPlaybackRate = function () {
  16278. // Playback rate API is implemented in Android Chrome, but doesn't do anything
  16279. // https://github.com/videojs/video.js/issues/3180
  16280. if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
  16281. return false;
  16282. } // IE will error if Windows Media Player not installed #3315
  16283. try {
  16284. var playbackRate = Html5.TEST_VID.playbackRate;
  16285. Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
  16286. return playbackRate !== Html5.TEST_VID.playbackRate;
  16287. } catch (e) {
  16288. return false;
  16289. }
  16290. };
  16291. /**
  16292. * Check if we can override a video/audio elements attributes, with
  16293. * Object.defineProperty.
  16294. *
  16295. * @return {boolean}
  16296. * - True if builtin attributes can be overridden
  16297. * - False otherwise
  16298. */
  16299. Html5.canOverrideAttributes = function () {
  16300. // if we cannot overwrite the src/innerHTML property, there is no support
  16301. // iOS 7 safari for instance cannot do this.
  16302. try {
  16303. var noop = function noop() {};
  16304. Object.defineProperty(document.createElement('video'), 'src', {
  16305. get: noop,
  16306. set: noop
  16307. });
  16308. Object.defineProperty(document.createElement('audio'), 'src', {
  16309. get: noop,
  16310. set: noop
  16311. });
  16312. Object.defineProperty(document.createElement('video'), 'innerHTML', {
  16313. get: noop,
  16314. set: noop
  16315. });
  16316. Object.defineProperty(document.createElement('audio'), 'innerHTML', {
  16317. get: noop,
  16318. set: noop
  16319. });
  16320. } catch (e) {
  16321. return false;
  16322. }
  16323. return true;
  16324. };
  16325. /**
  16326. * Check to see if native `TextTrack`s are supported by this browser/device.
  16327. *
  16328. * @return {boolean}
  16329. * - True if native `TextTrack`s are supported.
  16330. * - False otherwise
  16331. */
  16332. Html5.supportsNativeTextTracks = function () {
  16333. return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
  16334. };
  16335. /**
  16336. * Check to see if native `VideoTrack`s are supported by this browser/device
  16337. *
  16338. * @return {boolean}
  16339. * - True if native `VideoTrack`s are supported.
  16340. * - False otherwise
  16341. */
  16342. Html5.supportsNativeVideoTracks = function () {
  16343. return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
  16344. };
  16345. /**
  16346. * Check to see if native `AudioTrack`s are supported by this browser/device
  16347. *
  16348. * @return {boolean}
  16349. * - True if native `AudioTrack`s are supported.
  16350. * - False otherwise
  16351. */
  16352. Html5.supportsNativeAudioTracks = function () {
  16353. return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
  16354. };
  16355. /**
  16356. * An array of events available on the Html5 tech.
  16357. *
  16358. * @private
  16359. * @type {Array}
  16360. */
  16361. 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'];
  16362. /**
  16363. * Boolean indicating whether the `Tech` supports volume control.
  16364. *
  16365. * @type {boolean}
  16366. * @default {@link Html5.canControlVolume}
  16367. */
  16368. Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
  16369. /**
  16370. * Boolean indicating whether the `Tech` supports muting volume.
  16371. *
  16372. * @type {bolean}
  16373. * @default {@link Html5.canMuteVolume}
  16374. */
  16375. Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
  16376. /**
  16377. * Boolean indicating whether the `Tech` supports changing the speed at which the media
  16378. * plays. Examples:
  16379. * - Set player to play 2x (twice) as fast
  16380. * - Set player to play 0.5x (half) as fast
  16381. *
  16382. * @type {boolean}
  16383. * @default {@link Html5.canControlPlaybackRate}
  16384. */
  16385. Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
  16386. /**
  16387. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  16388. *
  16389. * @type {boolean}
  16390. * @default
  16391. */
  16392. Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
  16393. /**
  16394. * Boolean indicating whether the `HTML5` tech currently supports the media element
  16395. * moving in the DOM. iOS breaks if you move the media element, so this is set this to
  16396. * false there. Everywhere else this should be true.
  16397. *
  16398. * @type {boolean}
  16399. * @default
  16400. */
  16401. Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
  16402. // Is this true?
  16403. /**
  16404. * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
  16405. * when going into fullscreen.
  16406. *
  16407. * @type {boolean}
  16408. * @default
  16409. */
  16410. Html5.prototype.featuresFullscreenResize = true;
  16411. /**
  16412. * Boolean indicating whether the `HTML5` tech currently supports the progress event.
  16413. * If this is false, manual `progress` events will be triggered instead.
  16414. *
  16415. * @type {boolean}
  16416. * @default
  16417. */
  16418. Html5.prototype.featuresProgressEvents = true;
  16419. /**
  16420. * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
  16421. * If this is false, manual `timeupdate` events will be triggered instead.
  16422. *
  16423. * @default
  16424. */
  16425. Html5.prototype.featuresTimeupdateEvents = true;
  16426. /**
  16427. * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
  16428. *
  16429. * @type {boolean}
  16430. * @default {@link Html5.supportsNativeTextTracks}
  16431. */
  16432. Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
  16433. /**
  16434. * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
  16435. *
  16436. * @type {boolean}
  16437. * @default {@link Html5.supportsNativeVideoTracks}
  16438. */
  16439. Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
  16440. /**
  16441. * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
  16442. *
  16443. * @type {boolean}
  16444. * @default {@link Html5.supportsNativeAudioTracks}
  16445. */
  16446. Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); // HTML5 Feature detection and Device Fixes --------------------------------- //
  16447. var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
  16448. var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
  16449. Html5.patchCanPlayType = function () {
  16450. // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
  16451. // Firefox and Chrome report correctly
  16452. if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
  16453. Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
  16454. if (type && mpegurlRE.test(type)) {
  16455. return 'maybe';
  16456. }
  16457. return canPlayType.call(this, type);
  16458. };
  16459. }
  16460. };
  16461. Html5.unpatchCanPlayType = function () {
  16462. var r = Html5.TEST_VID.constructor.prototype.canPlayType;
  16463. Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  16464. return r;
  16465. }; // by default, patch the media element
  16466. Html5.patchCanPlayType();
  16467. Html5.disposeMediaElement = function (el) {
  16468. if (!el) {
  16469. return;
  16470. }
  16471. if (el.parentNode) {
  16472. el.parentNode.removeChild(el);
  16473. } // remove any child track or source nodes to prevent their loading
  16474. while (el.hasChildNodes()) {
  16475. el.removeChild(el.firstChild);
  16476. } // remove any src reference. not setting `src=''` because that causes a warning
  16477. // in firefox
  16478. el.removeAttribute('src'); // force the media element to update its loading state by calling load()
  16479. // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
  16480. if (typeof el.load === 'function') {
  16481. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  16482. (function () {
  16483. try {
  16484. el.load();
  16485. } catch (e) {// not supported
  16486. }
  16487. })();
  16488. }
  16489. };
  16490. Html5.resetMediaElement = function (el) {
  16491. if (!el) {
  16492. return;
  16493. }
  16494. var sources = el.querySelectorAll('source');
  16495. var i = sources.length;
  16496. while (i--) {
  16497. el.removeChild(sources[i]);
  16498. } // remove any src reference.
  16499. // not setting `src=''` because that throws an error
  16500. el.removeAttribute('src');
  16501. if (typeof el.load === 'function') {
  16502. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  16503. (function () {
  16504. try {
  16505. el.load();
  16506. } catch (e) {// satisfy linter
  16507. }
  16508. })();
  16509. }
  16510. };
  16511. /* Native HTML5 element property wrapping ----------------------------------- */
  16512. // Wrap native boolean attributes with getters that check both property and attribute
  16513. // The list is as followed:
  16514. // muted, defaultMuted, autoplay, controls, loop, playsinline
  16515. [
  16516. /**
  16517. * Get the value of `muted` from the media element. `muted` indicates
  16518. * that the volume for the media should be set to silent. This does not actually change
  16519. * the `volume` attribute.
  16520. *
  16521. * @method Html5#muted
  16522. * @return {boolean}
  16523. * - True if the value of `volume` should be ignored and the audio set to silent.
  16524. * - False if the value of `volume` should be used.
  16525. *
  16526. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  16527. */
  16528. 'muted',
  16529. /**
  16530. * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
  16531. * whether the media should start muted or not. Only changes the default state of the
  16532. * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
  16533. * current state.
  16534. *
  16535. * @method Html5#defaultMuted
  16536. * @return {boolean}
  16537. * - The value of `defaultMuted` from the media element.
  16538. * - True indicates that the media should start muted.
  16539. * - False indicates that the media should not start muted
  16540. *
  16541. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  16542. */
  16543. 'defaultMuted',
  16544. /**
  16545. * Get the value of `autoplay` from the media element. `autoplay` indicates
  16546. * that the media should start to play as soon as the page is ready.
  16547. *
  16548. * @method Html5#autoplay
  16549. * @return {boolean}
  16550. * - The value of `autoplay` from the media element.
  16551. * - True indicates that the media should start as soon as the page loads.
  16552. * - False indicates that the media should not start as soon as the page loads.
  16553. *
  16554. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  16555. */
  16556. 'autoplay',
  16557. /**
  16558. * Get the value of `controls` from the media element. `controls` indicates
  16559. * whether the native media controls should be shown or hidden.
  16560. *
  16561. * @method Html5#controls
  16562. * @return {boolean}
  16563. * - The value of `controls` from the media element.
  16564. * - True indicates that native controls should be showing.
  16565. * - False indicates that native controls should be hidden.
  16566. *
  16567. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
  16568. */
  16569. 'controls',
  16570. /**
  16571. * Get the value of `loop` from the media element. `loop` indicates
  16572. * that the media should return to the start of the media and continue playing once
  16573. * it reaches the end.
  16574. *
  16575. * @method Html5#loop
  16576. * @return {boolean}
  16577. * - The value of `loop` from the media element.
  16578. * - True indicates that playback should seek back to start once
  16579. * the end of a media is reached.
  16580. * - False indicates that playback should not loop back to the start when the
  16581. * end of the media is reached.
  16582. *
  16583. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  16584. */
  16585. 'loop',
  16586. /**
  16587. * Get the value of `playsinline` from the media element. `playsinline` indicates
  16588. * to the browser that non-fullscreen playback is preferred when fullscreen
  16589. * playback is the native default, such as in iOS Safari.
  16590. *
  16591. * @method Html5#playsinline
  16592. * @return {boolean}
  16593. * - The value of `playsinline` from the media element.
  16594. * - True indicates that the media should play inline.
  16595. * - False indicates that the media should not play inline.
  16596. *
  16597. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  16598. */
  16599. 'playsinline'].forEach(function (prop) {
  16600. Html5.prototype[prop] = function () {
  16601. return this.el_[prop] || this.el_.hasAttribute(prop);
  16602. };
  16603. }); // Wrap native boolean attributes with setters that set both property and attribute
  16604. // The list is as followed:
  16605. // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
  16606. // setControls is special-cased above
  16607. [
  16608. /**
  16609. * Set the value of `muted` on the media element. `muted` indicates that the current
  16610. * audio level should be silent.
  16611. *
  16612. * @method Html5#setMuted
  16613. * @param {boolean} muted
  16614. * - True if the audio should be set to silent
  16615. * - False otherwise
  16616. *
  16617. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  16618. */
  16619. 'muted',
  16620. /**
  16621. * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
  16622. * audio level should be silent, but will only effect the muted level on intial playback..
  16623. *
  16624. * @method Html5.prototype.setDefaultMuted
  16625. * @param {boolean} defaultMuted
  16626. * - True if the audio should be set to silent
  16627. * - False otherwise
  16628. *
  16629. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  16630. */
  16631. 'defaultMuted',
  16632. /**
  16633. * Set the value of `autoplay` on the media element. `autoplay` indicates
  16634. * that the media should start to play as soon as the page is ready.
  16635. *
  16636. * @method Html5#setAutoplay
  16637. * @param {boolean} autoplay
  16638. * - True indicates that the media should start as soon as the page loads.
  16639. * - False indicates that the media should not start as soon as the page loads.
  16640. *
  16641. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  16642. */
  16643. 'autoplay',
  16644. /**
  16645. * Set the value of `loop` on the media element. `loop` indicates
  16646. * that the media should return to the start of the media and continue playing once
  16647. * it reaches the end.
  16648. *
  16649. * @method Html5#setLoop
  16650. * @param {boolean} loop
  16651. * - True indicates that playback should seek back to start once
  16652. * the end of a media is reached.
  16653. * - False indicates that playback should not loop back to the start when the
  16654. * end of the media is reached.
  16655. *
  16656. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  16657. */
  16658. 'loop',
  16659. /**
  16660. * Set the value of `playsinline` from the media element. `playsinline` indicates
  16661. * to the browser that non-fullscreen playback is preferred when fullscreen
  16662. * playback is the native default, such as in iOS Safari.
  16663. *
  16664. * @method Html5#setPlaysinline
  16665. * @param {boolean} playsinline
  16666. * - True indicates that the media should play inline.
  16667. * - False indicates that the media should not play inline.
  16668. *
  16669. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  16670. */
  16671. 'playsinline'].forEach(function (prop) {
  16672. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  16673. this.el_[prop] = v;
  16674. if (v) {
  16675. this.el_.setAttribute(prop, prop);
  16676. } else {
  16677. this.el_.removeAttribute(prop);
  16678. }
  16679. };
  16680. }); // Wrap native properties with a getter
  16681. // The list is as followed
  16682. // paused, currentTime, buffered, volume, poster, preload, error, seeking
  16683. // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
  16684. // readyState, videoWidth, videoHeight
  16685. [
  16686. /**
  16687. * Get the value of `paused` from the media element. `paused` indicates whether the media element
  16688. * is currently paused or not.
  16689. *
  16690. * @method Html5#paused
  16691. * @return {boolean}
  16692. * The value of `paused` from the media element.
  16693. *
  16694. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
  16695. */
  16696. 'paused',
  16697. /**
  16698. * Get the value of `currentTime` from the media element. `currentTime` indicates
  16699. * the current second that the media is at in playback.
  16700. *
  16701. * @method Html5#currentTime
  16702. * @return {number}
  16703. * The value of `currentTime` from the media element.
  16704. *
  16705. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
  16706. */
  16707. 'currentTime',
  16708. /**
  16709. * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
  16710. * object that represents the parts of the media that are already downloaded and
  16711. * available for playback.
  16712. *
  16713. * @method Html5#buffered
  16714. * @return {TimeRange}
  16715. * The value of `buffered` from the media element.
  16716. *
  16717. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
  16718. */
  16719. 'buffered',
  16720. /**
  16721. * Get the value of `volume` from the media element. `volume` indicates
  16722. * the current playback volume of audio for a media. `volume` will be a value from 0
  16723. * (silent) to 1 (loudest and default).
  16724. *
  16725. * @method Html5#volume
  16726. * @return {number}
  16727. * The value of `volume` from the media element. Value will be between 0-1.
  16728. *
  16729. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  16730. */
  16731. 'volume',
  16732. /**
  16733. * Get the value of `poster` from the media element. `poster` indicates
  16734. * that the url of an image file that can/will be shown when no media data is available.
  16735. *
  16736. * @method Html5#poster
  16737. * @return {string}
  16738. * The value of `poster` from the media element. Value will be a url to an
  16739. * image.
  16740. *
  16741. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
  16742. */
  16743. 'poster',
  16744. /**
  16745. * Get the value of `preload` from the media element. `preload` indicates
  16746. * what should download before the media is interacted with. It can have the following
  16747. * values:
  16748. * - none: nothing should be downloaded
  16749. * - metadata: poster and the first few frames of the media may be downloaded to get
  16750. * media dimensions and other metadata
  16751. * - auto: allow the media and metadata for the media to be downloaded before
  16752. * interaction
  16753. *
  16754. * @method Html5#preload
  16755. * @return {string}
  16756. * The value of `preload` from the media element. Will be 'none', 'metadata',
  16757. * or 'auto'.
  16758. *
  16759. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  16760. */
  16761. 'preload',
  16762. /**
  16763. * Get the value of the `error` from the media element. `error` indicates any
  16764. * MediaError that may have occurred during playback. If error returns null there is no
  16765. * current error.
  16766. *
  16767. * @method Html5#error
  16768. * @return {MediaError|null}
  16769. * The value of `error` from the media element. Will be `MediaError` if there
  16770. * is a current error and null otherwise.
  16771. *
  16772. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
  16773. */
  16774. 'error',
  16775. /**
  16776. * Get the value of `seeking` from the media element. `seeking` indicates whether the
  16777. * media is currently seeking to a new position or not.
  16778. *
  16779. * @method Html5#seeking
  16780. * @return {boolean}
  16781. * - The value of `seeking` from the media element.
  16782. * - True indicates that the media is currently seeking to a new position.
  16783. * - False indicates that the media is not seeking to a new position at this time.
  16784. *
  16785. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
  16786. */
  16787. 'seeking',
  16788. /**
  16789. * Get the value of `seekable` from the media element. `seekable` returns a
  16790. * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
  16791. *
  16792. * @method Html5#seekable
  16793. * @return {TimeRange}
  16794. * The value of `seekable` from the media element. A `TimeRange` object
  16795. * indicating the current ranges of time that can be seeked to.
  16796. *
  16797. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
  16798. */
  16799. 'seekable',
  16800. /**
  16801. * Get the value of `ended` from the media element. `ended` indicates whether
  16802. * the media has reached the end or not.
  16803. *
  16804. * @method Html5#ended
  16805. * @return {boolean}
  16806. * - The value of `ended` from the media element.
  16807. * - True indicates that the media has ended.
  16808. * - False indicates that the media has not ended.
  16809. *
  16810. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
  16811. */
  16812. 'ended',
  16813. /**
  16814. * Get the value of `playbackRate` from the media element. `playbackRate` indicates
  16815. * the rate at which the media is currently playing back. Examples:
  16816. * - if playbackRate is set to 2, media will play twice as fast.
  16817. * - if playbackRate is set to 0.5, media will play half as fast.
  16818. *
  16819. * @method Html5#playbackRate
  16820. * @return {number}
  16821. * The value of `playbackRate` from the media element. A number indicating
  16822. * the current playback speed of the media, where 1 is normal speed.
  16823. *
  16824. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  16825. */
  16826. 'playbackRate',
  16827. /**
  16828. * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
  16829. * the rate at which the media is currently playing back. This value will not indicate the current
  16830. * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
  16831. *
  16832. * Examples:
  16833. * - if defaultPlaybackRate is set to 2, media will play twice as fast.
  16834. * - if defaultPlaybackRate is set to 0.5, media will play half as fast.
  16835. *
  16836. * @method Html5.prototype.defaultPlaybackRate
  16837. * @return {number}
  16838. * The value of `defaultPlaybackRate` from the media element. A number indicating
  16839. * the current playback speed of the media, where 1 is normal speed.
  16840. *
  16841. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  16842. */
  16843. 'defaultPlaybackRate',
  16844. /**
  16845. * Get the value of `played` from the media element. `played` returns a `TimeRange`
  16846. * object representing points in the media timeline that have been played.
  16847. *
  16848. * @method Html5#played
  16849. * @return {TimeRange}
  16850. * The value of `played` from the media element. A `TimeRange` object indicating
  16851. * the ranges of time that have been played.
  16852. *
  16853. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
  16854. */
  16855. 'played',
  16856. /**
  16857. * Get the value of `networkState` from the media element. `networkState` indicates
  16858. * the current network state. It returns an enumeration from the following list:
  16859. * - 0: NETWORK_EMPTY
  16860. * - 1: NETWORK_IDLE
  16861. * - 2: NETWORK_LOADING
  16862. * - 3: NETWORK_NO_SOURCE
  16863. *
  16864. * @method Html5#networkState
  16865. * @return {number}
  16866. * The value of `networkState` from the media element. This will be a number
  16867. * from the list in the description.
  16868. *
  16869. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
  16870. */
  16871. 'networkState',
  16872. /**
  16873. * Get the value of `readyState` from the media element. `readyState` indicates
  16874. * the current state of the media element. It returns an enumeration from the
  16875. * following list:
  16876. * - 0: HAVE_NOTHING
  16877. * - 1: HAVE_METADATA
  16878. * - 2: HAVE_CURRENT_DATA
  16879. * - 3: HAVE_FUTURE_DATA
  16880. * - 4: HAVE_ENOUGH_DATA
  16881. *
  16882. * @method Html5#readyState
  16883. * @return {number}
  16884. * The value of `readyState` from the media element. This will be a number
  16885. * from the list in the description.
  16886. *
  16887. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
  16888. */
  16889. 'readyState',
  16890. /**
  16891. * Get the value of `videoWidth` from the video element. `videoWidth` indicates
  16892. * the current width of the video in css pixels.
  16893. *
  16894. * @method Html5#videoWidth
  16895. * @return {number}
  16896. * The value of `videoWidth` from the video element. This will be a number
  16897. * in css pixels.
  16898. *
  16899. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  16900. */
  16901. 'videoWidth',
  16902. /**
  16903. * Get the value of `videoHeight` from the video element. `videoHeight` indicates
  16904. * the current height of the video in css pixels.
  16905. *
  16906. * @method Html5#videoHeight
  16907. * @return {number}
  16908. * The value of `videoHeight` from the video element. This will be a number
  16909. * in css pixels.
  16910. *
  16911. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  16912. */
  16913. 'videoHeight'].forEach(function (prop) {
  16914. Html5.prototype[prop] = function () {
  16915. return this.el_[prop];
  16916. };
  16917. }); // Wrap native properties with a setter in this format:
  16918. // set + toTitleCase(name)
  16919. // The list is as follows:
  16920. // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
  16921. [
  16922. /**
  16923. * Set the value of `volume` on the media element. `volume` indicates the current
  16924. * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
  16925. * so on.
  16926. *
  16927. * @method Html5#setVolume
  16928. * @param {number} percentAsDecimal
  16929. * The volume percent as a decimal. Valid range is from 0-1.
  16930. *
  16931. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  16932. */
  16933. 'volume',
  16934. /**
  16935. * Set the value of `src` on the media element. `src` indicates the current
  16936. * {@link Tech~SourceObject} for the media.
  16937. *
  16938. * @method Html5#setSrc
  16939. * @param {Tech~SourceObject} src
  16940. * The source object to set as the current source.
  16941. *
  16942. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
  16943. */
  16944. 'src',
  16945. /**
  16946. * Set the value of `poster` on the media element. `poster` is the url to
  16947. * an image file that can/will be shown when no media data is available.
  16948. *
  16949. * @method Html5#setPoster
  16950. * @param {string} poster
  16951. * The url to an image that should be used as the `poster` for the media
  16952. * element.
  16953. *
  16954. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
  16955. */
  16956. 'poster',
  16957. /**
  16958. * Set the value of `preload` on the media element. `preload` indicates
  16959. * what should download before the media is interacted with. It can have the following
  16960. * values:
  16961. * - none: nothing should be downloaded
  16962. * - metadata: poster and the first few frames of the media may be downloaded to get
  16963. * media dimensions and other metadata
  16964. * - auto: allow the media and metadata for the media to be downloaded before
  16965. * interaction
  16966. *
  16967. * @method Html5#setPreload
  16968. * @param {string} preload
  16969. * The value of `preload` to set on the media element. Must be 'none', 'metadata',
  16970. * or 'auto'.
  16971. *
  16972. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  16973. */
  16974. 'preload',
  16975. /**
  16976. * Set the value of `playbackRate` on the media element. `playbackRate` indicates
  16977. * the rate at which the media should play back. Examples:
  16978. * - if playbackRate is set to 2, media will play twice as fast.
  16979. * - if playbackRate is set to 0.5, media will play half as fast.
  16980. *
  16981. * @method Html5#setPlaybackRate
  16982. * @return {number}
  16983. * The value of `playbackRate` from the media element. A number indicating
  16984. * the current playback speed of the media, where 1 is normal speed.
  16985. *
  16986. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  16987. */
  16988. 'playbackRate',
  16989. /**
  16990. * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
  16991. * the rate at which the media should play back upon initial startup. Changing this value
  16992. * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
  16993. *
  16994. * Example Values:
  16995. * - if playbackRate is set to 2, media will play twice as fast.
  16996. * - if playbackRate is set to 0.5, media will play half as fast.
  16997. *
  16998. * @method Html5.prototype.setDefaultPlaybackRate
  16999. * @return {number}
  17000. * The value of `defaultPlaybackRate` from the media element. A number indicating
  17001. * the current playback speed of the media, where 1 is normal speed.
  17002. *
  17003. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
  17004. */
  17005. 'defaultPlaybackRate'].forEach(function (prop) {
  17006. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  17007. this.el_[prop] = v;
  17008. };
  17009. }); // wrap native functions with a function
  17010. // The list is as follows:
  17011. // pause, load, play
  17012. [
  17013. /**
  17014. * A wrapper around the media elements `pause` function. This will call the `HTML5`
  17015. * media elements `pause` function.
  17016. *
  17017. * @method Html5#pause
  17018. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
  17019. */
  17020. 'pause',
  17021. /**
  17022. * A wrapper around the media elements `load` function. This will call the `HTML5`s
  17023. * media element `load` function.
  17024. *
  17025. * @method Html5#load
  17026. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
  17027. */
  17028. 'load',
  17029. /**
  17030. * A wrapper around the media elements `play` function. This will call the `HTML5`s
  17031. * media element `play` function.
  17032. *
  17033. * @method Html5#play
  17034. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
  17035. */
  17036. 'play'].forEach(function (prop) {
  17037. Html5.prototype[prop] = function () {
  17038. return this.el_[prop]();
  17039. };
  17040. });
  17041. Tech.withSourceHandlers(Html5);
  17042. /**
  17043. * Native source handler for Html5, simply passes the source to the media element.
  17044. *
  17045. * @property {Tech~SourceObject} source
  17046. * The source object
  17047. *
  17048. * @property {Html5} tech
  17049. * The instance of the HTML5 tech.
  17050. */
  17051. Html5.nativeSourceHandler = {};
  17052. /**
  17053. * Check if the media element can play the given mime type.
  17054. *
  17055. * @param {string} type
  17056. * The mimetype to check
  17057. *
  17058. * @return {string}
  17059. * 'probably', 'maybe', or '' (empty string)
  17060. */
  17061. Html5.nativeSourceHandler.canPlayType = function (type) {
  17062. // IE without MediaPlayer throws an error (#519)
  17063. try {
  17064. return Html5.TEST_VID.canPlayType(type);
  17065. } catch (e) {
  17066. return '';
  17067. }
  17068. };
  17069. /**
  17070. * Check if the media element can handle a source natively.
  17071. *
  17072. * @param {Tech~SourceObject} source
  17073. * The source object
  17074. *
  17075. * @param {Object} [options]
  17076. * Options to be passed to the tech.
  17077. *
  17078. * @return {string}
  17079. * 'probably', 'maybe', or '' (empty string).
  17080. */
  17081. Html5.nativeSourceHandler.canHandleSource = function (source, options) {
  17082. // If a type was provided we should rely on that
  17083. if (source.type) {
  17084. return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
  17085. } else if (source.src) {
  17086. var ext = getFileExtension(source.src);
  17087. return Html5.nativeSourceHandler.canPlayType("video/" + ext);
  17088. }
  17089. return '';
  17090. };
  17091. /**
  17092. * Pass the source to the native media element.
  17093. *
  17094. * @param {Tech~SourceObject} source
  17095. * The source object
  17096. *
  17097. * @param {Html5} tech
  17098. * The instance of the Html5 tech
  17099. *
  17100. * @param {Object} [options]
  17101. * The options to pass to the source
  17102. */
  17103. Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
  17104. tech.setSrc(source.src);
  17105. };
  17106. /**
  17107. * A noop for the native dispose function, as cleanup is not needed.
  17108. */
  17109. Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
  17110. Html5.registerSourceHandler(Html5.nativeSourceHandler);
  17111. Tech.registerTech('Html5', Html5);
  17112. function _templateObject$2() {
  17113. 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 "]);
  17114. _templateObject$2 = function _templateObject() {
  17115. return data;
  17116. };
  17117. return data;
  17118. }
  17119. // on the player when they happen
  17120. var TECH_EVENTS_RETRIGGER = [
  17121. /**
  17122. * Fired while the user agent is downloading media data.
  17123. *
  17124. * @event Player#progress
  17125. * @type {EventTarget~Event}
  17126. */
  17127. /**
  17128. * Retrigger the `progress` event that was triggered by the {@link Tech}.
  17129. *
  17130. * @private
  17131. * @method Player#handleTechProgress_
  17132. * @fires Player#progress
  17133. * @listens Tech#progress
  17134. */
  17135. 'progress',
  17136. /**
  17137. * Fires when the loading of an audio/video is aborted.
  17138. *
  17139. * @event Player#abort
  17140. * @type {EventTarget~Event}
  17141. */
  17142. /**
  17143. * Retrigger the `abort` event that was triggered by the {@link Tech}.
  17144. *
  17145. * @private
  17146. * @method Player#handleTechAbort_
  17147. * @fires Player#abort
  17148. * @listens Tech#abort
  17149. */
  17150. 'abort',
  17151. /**
  17152. * Fires when the browser is intentionally not getting media data.
  17153. *
  17154. * @event Player#suspend
  17155. * @type {EventTarget~Event}
  17156. */
  17157. /**
  17158. * Retrigger the `suspend` event that was triggered by the {@link Tech}.
  17159. *
  17160. * @private
  17161. * @method Player#handleTechSuspend_
  17162. * @fires Player#suspend
  17163. * @listens Tech#suspend
  17164. */
  17165. 'suspend',
  17166. /**
  17167. * Fires when the current playlist is empty.
  17168. *
  17169. * @event Player#emptied
  17170. * @type {EventTarget~Event}
  17171. */
  17172. /**
  17173. * Retrigger the `emptied` event that was triggered by the {@link Tech}.
  17174. *
  17175. * @private
  17176. * @method Player#handleTechEmptied_
  17177. * @fires Player#emptied
  17178. * @listens Tech#emptied
  17179. */
  17180. 'emptied',
  17181. /**
  17182. * Fires when the browser is trying to get media data, but data is not available.
  17183. *
  17184. * @event Player#stalled
  17185. * @type {EventTarget~Event}
  17186. */
  17187. /**
  17188. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  17189. *
  17190. * @private
  17191. * @method Player#handleTechStalled_
  17192. * @fires Player#stalled
  17193. * @listens Tech#stalled
  17194. */
  17195. 'stalled',
  17196. /**
  17197. * Fires when the browser has loaded meta data for the audio/video.
  17198. *
  17199. * @event Player#loadedmetadata
  17200. * @type {EventTarget~Event}
  17201. */
  17202. /**
  17203. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  17204. *
  17205. * @private
  17206. * @method Player#handleTechLoadedmetadata_
  17207. * @fires Player#loadedmetadata
  17208. * @listens Tech#loadedmetadata
  17209. */
  17210. 'loadedmetadata',
  17211. /**
  17212. * Fires when the browser has loaded the current frame of the audio/video.
  17213. *
  17214. * @event Player#loadeddata
  17215. * @type {event}
  17216. */
  17217. /**
  17218. * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
  17219. *
  17220. * @private
  17221. * @method Player#handleTechLoaddeddata_
  17222. * @fires Player#loadeddata
  17223. * @listens Tech#loadeddata
  17224. */
  17225. 'loadeddata',
  17226. /**
  17227. * Fires when the current playback position has changed.
  17228. *
  17229. * @event Player#timeupdate
  17230. * @type {event}
  17231. */
  17232. /**
  17233. * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
  17234. *
  17235. * @private
  17236. * @method Player#handleTechTimeUpdate_
  17237. * @fires Player#timeupdate
  17238. * @listens Tech#timeupdate
  17239. */
  17240. 'timeupdate',
  17241. /**
  17242. * Fires when the video's intrinsic dimensions change
  17243. *
  17244. * @event Player#resize
  17245. * @type {event}
  17246. */
  17247. /**
  17248. * Retrigger the `resize` event that was triggered by the {@link Tech}.
  17249. *
  17250. * @private
  17251. * @method Player#handleTechResize_
  17252. * @fires Player#resize
  17253. * @listens Tech#resize
  17254. */
  17255. 'resize',
  17256. /**
  17257. * Fires when the volume has been changed
  17258. *
  17259. * @event Player#volumechange
  17260. * @type {event}
  17261. */
  17262. /**
  17263. * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
  17264. *
  17265. * @private
  17266. * @method Player#handleTechVolumechange_
  17267. * @fires Player#volumechange
  17268. * @listens Tech#volumechange
  17269. */
  17270. 'volumechange',
  17271. /**
  17272. * Fires when the text track has been changed
  17273. *
  17274. * @event Player#texttrackchange
  17275. * @type {event}
  17276. */
  17277. /**
  17278. * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
  17279. *
  17280. * @private
  17281. * @method Player#handleTechTexttrackchange_
  17282. * @fires Player#texttrackchange
  17283. * @listens Tech#texttrackchange
  17284. */
  17285. 'texttrackchange']; // events to queue when playback rate is zero
  17286. // this is a hash for the sole purpose of mapping non-camel-cased event names
  17287. // to camel-cased function names
  17288. var TECH_EVENTS_QUEUE = {
  17289. canplay: 'CanPlay',
  17290. canplaythrough: 'CanPlayThrough',
  17291. playing: 'Playing',
  17292. seeked: 'Seeked'
  17293. };
  17294. var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
  17295. var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
  17296. // grep: vjs-layout-x-small
  17297. // grep: vjs-layout-small
  17298. // grep: vjs-layout-medium
  17299. // grep: vjs-layout-large
  17300. // grep: vjs-layout-x-large
  17301. // grep: vjs-layout-huge
  17302. BREAKPOINT_ORDER.forEach(function (k) {
  17303. var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
  17304. BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
  17305. });
  17306. var DEFAULT_BREAKPOINTS = {
  17307. tiny: 210,
  17308. xsmall: 320,
  17309. small: 425,
  17310. medium: 768,
  17311. large: 1440,
  17312. xlarge: 2560,
  17313. huge: Infinity
  17314. };
  17315. /**
  17316. * An instance of the `Player` class is created when any of the Video.js setup methods
  17317. * are used to initialize a video.
  17318. *
  17319. * After an instance has been created it can be accessed globally in two ways:
  17320. * 1. By calling `videojs('example_video_1');`
  17321. * 2. By using it directly via `videojs.players.example_video_1;`
  17322. *
  17323. * @extends Component
  17324. */
  17325. var Player =
  17326. /*#__PURE__*/
  17327. function (_Component) {
  17328. _inheritsLoose(Player, _Component);
  17329. /**
  17330. * Create an instance of this class.
  17331. *
  17332. * @param {Element} tag
  17333. * The original video DOM element used for configuring options.
  17334. *
  17335. * @param {Object} [options]
  17336. * Object of option names and values.
  17337. *
  17338. * @param {Component~ReadyCallback} [ready]
  17339. * Ready callback function.
  17340. */
  17341. function Player(tag, options, ready) {
  17342. var _this;
  17343. // Make sure tag ID exists
  17344. tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
  17345. // The options argument overrides options set in the video tag
  17346. // which overrides globally set options.
  17347. // This latter part coincides with the load order
  17348. // (tag must exist before Player)
  17349. options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
  17350. // player properties first, and can't use `this` before `super()`
  17351. options.initChildren = false; // Same with creating the element
  17352. options.createEl = false; // don't auto mixin the evented mixin
  17353. options.evented = false; // we don't want the player to report touch activity on itself
  17354. // see enableTouchActivity in Component
  17355. options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
  17356. if (!options.language) {
  17357. if (typeof tag.closest === 'function') {
  17358. var closest = tag.closest('[lang]');
  17359. if (closest && closest.getAttribute) {
  17360. options.language = closest.getAttribute('lang');
  17361. }
  17362. } else {
  17363. var element = tag;
  17364. while (element && element.nodeType === 1) {
  17365. if (getAttributes(element).hasOwnProperty('lang')) {
  17366. options.language = element.getAttribute('lang');
  17367. break;
  17368. }
  17369. element = element.parentNode;
  17370. }
  17371. }
  17372. } // Run base component initializing with new options
  17373. _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
  17374. _this.boundDocumentFullscreenChange_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.documentFullscreenChange_);
  17375. _this.boundFullWindowOnEscKey_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.fullWindowOnEscKey);
  17376. _this.boundHandleKeyPress_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleKeyPress); // create logger
  17377. _this.log = createLogger$1(_this.id_); // Tracks when a tech changes the poster
  17378. _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
  17379. // and a seek is happening
  17380. _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
  17381. _this.isReady_ = false; // Init state hasStarted_
  17382. _this.hasStarted_ = false; // Init state userActive_
  17383. _this.userActive_ = false; // if the global option object was accidentally blown away by
  17384. // someone, bail early with an informative error
  17385. if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
  17386. throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
  17387. } // Store the original tag used to set options
  17388. _this.tag = tag; // Store the tag attributes used to restore html5 element
  17389. _this.tagAttributes = tag && getAttributes(tag); // Update current language
  17390. _this.language(_this.options_.language); // Update Supported Languages
  17391. if (options.languages) {
  17392. // Normalise player option languages to lowercase
  17393. var languagesToLower = {};
  17394. Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
  17395. languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
  17396. });
  17397. _this.languages_ = languagesToLower;
  17398. } else {
  17399. _this.languages_ = Player.prototype.options_.languages;
  17400. }
  17401. _this.resetCache_(); // Set poster
  17402. _this.poster_ = options.poster || ''; // Set controls
  17403. _this.controls_ = !!options.controls; // Original tag settings stored in options
  17404. // now remove immediately so native controls don't flash.
  17405. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  17406. tag.controls = false;
  17407. tag.removeAttribute('controls');
  17408. _this.changingSrc_ = false;
  17409. _this.playCallbacks_ = [];
  17410. _this.playTerminatedQueue_ = []; // the attribute overrides the option
  17411. if (tag.hasAttribute('autoplay')) {
  17412. _this.autoplay(true);
  17413. } else {
  17414. // otherwise use the setter to validate and
  17415. // set the correct value.
  17416. _this.autoplay(_this.options_.autoplay);
  17417. } // check plugins
  17418. if (options.plugins) {
  17419. Object.keys(options.plugins).forEach(function (name$$1) {
  17420. if (typeof _this[name$$1] !== 'function') {
  17421. throw new Error("plugin \"" + name$$1 + "\" does not exist");
  17422. }
  17423. });
  17424. }
  17425. /*
  17426. * Store the internal state of scrubbing
  17427. *
  17428. * @private
  17429. * @return {Boolean} True if the user is scrubbing
  17430. */
  17431. _this.scrubbing_ = false;
  17432. _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
  17433. evented(_assertThisInitialized(_assertThisInitialized(_this)), {
  17434. eventBusKey: 'el_'
  17435. });
  17436. if (_this.fluid_) {
  17437. _this.on('playerreset', _this.updateStyleEl_);
  17438. } // We also want to pass the original player options to each component and plugin
  17439. // as well so they don't need to reach back into the player for options later.
  17440. // We also need to do another copy of this.options_ so we don't end up with
  17441. // an infinite loop.
  17442. var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins
  17443. if (options.plugins) {
  17444. Object.keys(options.plugins).forEach(function (name$$1) {
  17445. _this[name$$1](options.plugins[name$$1]);
  17446. });
  17447. }
  17448. _this.options_.playerOptions = playerOptionsCopy;
  17449. _this.middleware_ = [];
  17450. _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
  17451. _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
  17452. // set because the element doesn't exist yet.
  17453. if (_this.controls()) {
  17454. _this.addClass('vjs-controls-enabled');
  17455. } else {
  17456. _this.addClass('vjs-controls-disabled');
  17457. } // Set ARIA label and region role depending on player type
  17458. _this.el_.setAttribute('role', 'region');
  17459. if (_this.isAudio()) {
  17460. _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
  17461. } else {
  17462. _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
  17463. }
  17464. if (_this.isAudio()) {
  17465. _this.addClass('vjs-audio');
  17466. }
  17467. if (_this.flexNotSupported_()) {
  17468. _this.addClass('vjs-no-flex');
  17469. } // TODO: Make this smarter. Toggle user state between touching/mousing
  17470. // using events, since devices can have both touch and mouse events.
  17471. // TODO: Make this check be performed again when the window switches between monitors
  17472. // (See https://github.com/videojs/video.js/issues/5683)
  17473. if (TOUCH_ENABLED) {
  17474. _this.addClass('vjs-touch-enabled');
  17475. } // iOS Safari has broken hover handling
  17476. if (!IS_IOS) {
  17477. _this.addClass('vjs-workinghover');
  17478. } // Make player easily findable by ID
  17479. Player.players[_this.id_] = _assertThisInitialized(_assertThisInitialized(_this)); // Add a major version class to aid css in plugins
  17480. var majorVersion = version.split('.')[0];
  17481. _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
  17482. // like the control bar show themselves if needed
  17483. _this.userActive(true);
  17484. _this.reportUserActivity();
  17485. _this.one('play', _this.listenForUserActivity_);
  17486. _this.on('focus', _this.handleFocus);
  17487. _this.on('blur', _this.handleBlur);
  17488. _this.on('stageclick', _this.handleStageClick_);
  17489. _this.breakpoints(_this.options_.breakpoints);
  17490. _this.responsive(_this.options_.responsive);
  17491. return _this;
  17492. }
  17493. /**
  17494. * Destroys the video player and does any necessary cleanup.
  17495. *
  17496. * This is especially helpful if you are dynamically adding and removing videos
  17497. * to/from the DOM.
  17498. *
  17499. * @fires Player#dispose
  17500. */
  17501. var _proto = Player.prototype;
  17502. _proto.dispose = function dispose() {
  17503. var _this2 = this;
  17504. /**
  17505. * Called when the player is being disposed of.
  17506. *
  17507. * @event Player#dispose
  17508. * @type {EventTarget~Event}
  17509. */
  17510. this.trigger('dispose'); // prevent dispose from being called twice
  17511. this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
  17512. off(document, FullscreenApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  17513. off(document, 'keydown', this.boundFullWindowOnEscKey_);
  17514. off(document, 'keydown', this.boundHandleKeyPress_);
  17515. if (this.styleEl_ && this.styleEl_.parentNode) {
  17516. this.styleEl_.parentNode.removeChild(this.styleEl_);
  17517. this.styleEl_ = null;
  17518. } // Kill reference to this player
  17519. Player.players[this.id_] = null;
  17520. if (this.tag && this.tag.player) {
  17521. this.tag.player = null;
  17522. }
  17523. if (this.el_ && this.el_.player) {
  17524. this.el_.player = null;
  17525. }
  17526. if (this.tech_) {
  17527. this.tech_.dispose();
  17528. this.isPosterFromTech_ = false;
  17529. this.poster_ = '';
  17530. }
  17531. if (this.playerElIngest_) {
  17532. this.playerElIngest_ = null;
  17533. }
  17534. if (this.tag) {
  17535. this.tag = null;
  17536. }
  17537. clearCacheForPlayer(this); // remove all event handlers for track lists
  17538. // all tracks and track listeners are removed on
  17539. // tech dispose
  17540. ALL.names.forEach(function (name$$1) {
  17541. var props = ALL[name$$1];
  17542. var list = _this2[props.getterName](); // if it is not a native list
  17543. // we have to manually remove event listeners
  17544. if (list && list.off) {
  17545. list.off();
  17546. }
  17547. }); // the actual .el_ is removed here
  17548. _Component.prototype.dispose.call(this);
  17549. }
  17550. /**
  17551. * Create the `Player`'s DOM element.
  17552. *
  17553. * @return {Element}
  17554. * The DOM element that gets created.
  17555. */
  17556. ;
  17557. _proto.createEl = function createEl$$1() {
  17558. var tag = this.tag;
  17559. var el;
  17560. var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
  17561. var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
  17562. if (playerElIngest) {
  17563. el = this.el_ = tag.parentNode;
  17564. } else if (!divEmbed) {
  17565. el = this.el_ = _Component.prototype.createEl.call(this, 'div');
  17566. } // Copy over all the attributes from the tag, including ID and class
  17567. // ID will now reference player box, not the video tag
  17568. var attrs = getAttributes(tag);
  17569. if (divEmbed) {
  17570. el = this.el_ = tag;
  17571. tag = this.tag = document.createElement('video');
  17572. while (el.children.length) {
  17573. tag.appendChild(el.firstChild);
  17574. }
  17575. if (!hasClass(el, 'video-js')) {
  17576. addClass(el, 'video-js');
  17577. }
  17578. el.appendChild(tag);
  17579. playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
  17580. // to our new `video` element. This will move things like
  17581. // `src` or `controls` that were set via js before the player
  17582. // was initialized.
  17583. Object.keys(el).forEach(function (k) {
  17584. tag[k] = el[k];
  17585. });
  17586. } // set tabindex to -1 to remove the video element from the focus order
  17587. tag.setAttribute('tabindex', '-1');
  17588. attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and
  17589. // for the same issue with Chrome (on Windows) with JAWS.
  17590. // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
  17591. // Note that we can't detect if JAWS is being used, but this ARIA attribute
  17592. // doesn't change behavior of IE11 or Chrome if JAWS is not being used
  17593. if (IE_VERSION || IS_CHROME && IS_WINDOWS) {
  17594. tag.setAttribute('role', 'application');
  17595. attrs.role = 'application';
  17596. } // Remove width/height attrs from tag so CSS can make it 100% width/height
  17597. tag.removeAttribute('width');
  17598. tag.removeAttribute('height');
  17599. if ('width' in attrs) {
  17600. delete attrs.width;
  17601. }
  17602. if ('height' in attrs) {
  17603. delete attrs.height;
  17604. }
  17605. Object.getOwnPropertyNames(attrs).forEach(function (attr) {
  17606. // don't copy over the class attribute to the player element when we're in a div embed
  17607. // the class is already set up properly in the divEmbed case
  17608. // and we want to make sure that the `video-js` class doesn't get lost
  17609. if (!(divEmbed && attr === 'class')) {
  17610. el.setAttribute(attr, attrs[attr]);
  17611. }
  17612. if (divEmbed) {
  17613. tag.setAttribute(attr, attrs[attr]);
  17614. }
  17615. }); // Update tag id/class for use as HTML5 playback tech
  17616. // Might think we should do this after embedding in container so .vjs-tech class
  17617. // doesn't flash 100% width/height, but class only applies with .video-js parent
  17618. tag.playerId = tag.id;
  17619. tag.id += '_html5_api';
  17620. tag.className = 'vjs-tech'; // Make player findable on elements
  17621. tag.player = el.player = this; // Default state of video is paused
  17622. this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
  17623. // of the player in a way that's still overrideable by CSS, just like the
  17624. // video element
  17625. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
  17626. this.styleEl_ = createStyleElement('vjs-styles-dimensions');
  17627. var defaultsStyleEl = $('.vjs-styles-defaults');
  17628. var head = $('head');
  17629. head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
  17630. }
  17631. this.fill_ = false;
  17632. this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
  17633. this.width(this.options_.width);
  17634. this.height(this.options_.height);
  17635. this.fill(this.options_.fill);
  17636. this.fluid(this.options_.fluid);
  17637. this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag,
  17638. // because IE doesn't hide them completely from screen readers.
  17639. var links = tag.getElementsByTagName('a');
  17640. for (var i = 0; i < links.length; i++) {
  17641. var linkEl = links.item(i);
  17642. addClass(linkEl, 'vjs-hidden');
  17643. linkEl.setAttribute('hidden', 'hidden');
  17644. } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
  17645. // keep track of the original for later so we can know if the source originally failed
  17646. tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
  17647. if (tag.parentNode && !playerElIngest) {
  17648. tag.parentNode.insertBefore(el, tag);
  17649. } // insert the tag as the first child of the player element
  17650. // then manually add it to the children array so that this.addChild
  17651. // will work properly for other components
  17652. //
  17653. // Breaks iPhone, fixed in HTML5 setup.
  17654. prependTo(tag, el);
  17655. this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
  17656. // if it's been set to something different to the doc
  17657. this.el_.setAttribute('lang', this.language_);
  17658. this.el_ = el;
  17659. return el;
  17660. }
  17661. /**
  17662. * A getter/setter for the `Player`'s width. Returns the player's configured value.
  17663. * To get the current width use `currentWidth()`.
  17664. *
  17665. * @param {number} [value]
  17666. * The value to set the `Player`'s width to.
  17667. *
  17668. * @return {number}
  17669. * The current width of the `Player` when getting.
  17670. */
  17671. ;
  17672. _proto.width = function width(value) {
  17673. return this.dimension('width', value);
  17674. }
  17675. /**
  17676. * A getter/setter for the `Player`'s height. Returns the player's configured value.
  17677. * To get the current height use `currentheight()`.
  17678. *
  17679. * @param {number} [value]
  17680. * The value to set the `Player`'s heigth to.
  17681. *
  17682. * @return {number}
  17683. * The current height of the `Player` when getting.
  17684. */
  17685. ;
  17686. _proto.height = function height(value) {
  17687. return this.dimension('height', value);
  17688. }
  17689. /**
  17690. * A getter/setter for the `Player`'s width & height.
  17691. *
  17692. * @param {string} dimension
  17693. * This string can be:
  17694. * - 'width'
  17695. * - 'height'
  17696. *
  17697. * @param {number} [value]
  17698. * Value for dimension specified in the first argument.
  17699. *
  17700. * @return {number}
  17701. * The dimension arguments value when getting (width/height).
  17702. */
  17703. ;
  17704. _proto.dimension = function dimension(_dimension, value) {
  17705. var privDimension = _dimension + '_';
  17706. if (value === undefined) {
  17707. return this[privDimension] || 0;
  17708. }
  17709. if (value === '') {
  17710. // If an empty string is given, reset the dimension to be automatic
  17711. this[privDimension] = undefined;
  17712. this.updateStyleEl_();
  17713. return;
  17714. }
  17715. var parsedVal = parseFloat(value);
  17716. if (isNaN(parsedVal)) {
  17717. log.error("Improper value \"" + value + "\" supplied for for " + _dimension);
  17718. return;
  17719. }
  17720. this[privDimension] = parsedVal;
  17721. this.updateStyleEl_();
  17722. }
  17723. /**
  17724. * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
  17725. *
  17726. * Turning this on will turn off fill mode.
  17727. *
  17728. * @param {boolean} [bool]
  17729. * - A value of true adds the class.
  17730. * - A value of false removes the class.
  17731. * - No value will be a getter.
  17732. *
  17733. * @return {boolean|undefined}
  17734. * - The value of fluid when getting.
  17735. * - `undefined` when setting.
  17736. */
  17737. ;
  17738. _proto.fluid = function fluid(bool) {
  17739. if (bool === undefined) {
  17740. return !!this.fluid_;
  17741. }
  17742. this.fluid_ = !!bool;
  17743. if (isEvented(this)) {
  17744. this.off('playerreset', this.updateStyleEl_);
  17745. }
  17746. if (bool) {
  17747. this.addClass('vjs-fluid');
  17748. this.fill(false);
  17749. addEventedCallback(function () {
  17750. this.on('playerreset', this.updateStyleEl_);
  17751. });
  17752. } else {
  17753. this.removeClass('vjs-fluid');
  17754. }
  17755. this.updateStyleEl_();
  17756. }
  17757. /**
  17758. * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
  17759. *
  17760. * Turning this on will turn off fluid mode.
  17761. *
  17762. * @param {boolean} [bool]
  17763. * - A value of true adds the class.
  17764. * - A value of false removes the class.
  17765. * - No value will be a getter.
  17766. *
  17767. * @return {boolean|undefined}
  17768. * - The value of fluid when getting.
  17769. * - `undefined` when setting.
  17770. */
  17771. ;
  17772. _proto.fill = function fill(bool) {
  17773. if (bool === undefined) {
  17774. return !!this.fill_;
  17775. }
  17776. this.fill_ = !!bool;
  17777. if (bool) {
  17778. this.addClass('vjs-fill');
  17779. this.fluid(false);
  17780. } else {
  17781. this.removeClass('vjs-fill');
  17782. }
  17783. }
  17784. /**
  17785. * Get/Set the aspect ratio
  17786. *
  17787. * @param {string} [ratio]
  17788. * Aspect ratio for player
  17789. *
  17790. * @return {string|undefined}
  17791. * returns the current aspect ratio when getting
  17792. */
  17793. /**
  17794. * A getter/setter for the `Player`'s aspect ratio.
  17795. *
  17796. * @param {string} [ratio]
  17797. * The value to set the `Player's aspect ratio to.
  17798. *
  17799. * @return {string|undefined}
  17800. * - The current aspect ratio of the `Player` when getting.
  17801. * - undefined when setting
  17802. */
  17803. ;
  17804. _proto.aspectRatio = function aspectRatio(ratio) {
  17805. if (ratio === undefined) {
  17806. return this.aspectRatio_;
  17807. } // Check for width:height format
  17808. if (!/^\d+\:\d+$/.test(ratio)) {
  17809. throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
  17810. }
  17811. this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
  17812. // because in fixed mode you could calculate width and height yourself.
  17813. this.fluid(true);
  17814. this.updateStyleEl_();
  17815. }
  17816. /**
  17817. * Update styles of the `Player` element (height, width and aspect ratio).
  17818. *
  17819. * @private
  17820. * @listens Tech#loadedmetadata
  17821. */
  17822. ;
  17823. _proto.updateStyleEl_ = function updateStyleEl_() {
  17824. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
  17825. var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
  17826. var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
  17827. var techEl = this.tech_ && this.tech_.el();
  17828. if (techEl) {
  17829. if (_width >= 0) {
  17830. techEl.width = _width;
  17831. }
  17832. if (_height >= 0) {
  17833. techEl.height = _height;
  17834. }
  17835. }
  17836. return;
  17837. }
  17838. var width;
  17839. var height;
  17840. var aspectRatio;
  17841. var idClass; // The aspect ratio is either used directly or to calculate width and height.
  17842. if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
  17843. // Use any aspectRatio that's been specifically set
  17844. aspectRatio = this.aspectRatio_;
  17845. } else if (this.videoWidth() > 0) {
  17846. // Otherwise try to get the aspect ratio from the video metadata
  17847. aspectRatio = this.videoWidth() + ':' + this.videoHeight();
  17848. } else {
  17849. // Or use a default. The video element's is 2:1, but 16:9 is more common.
  17850. aspectRatio = '16:9';
  17851. } // Get the ratio as a decimal we can use to calculate dimensions
  17852. var ratioParts = aspectRatio.split(':');
  17853. var ratioMultiplier = ratioParts[1] / ratioParts[0];
  17854. if (this.width_ !== undefined) {
  17855. // Use any width that's been specifically set
  17856. width = this.width_;
  17857. } else if (this.height_ !== undefined) {
  17858. // Or calulate the width from the aspect ratio if a height has been set
  17859. width = this.height_ / ratioMultiplier;
  17860. } else {
  17861. // Or use the video's metadata, or use the video el's default of 300
  17862. width = this.videoWidth() || 300;
  17863. }
  17864. if (this.height_ !== undefined) {
  17865. // Use any height that's been specifically set
  17866. height = this.height_;
  17867. } else {
  17868. // Otherwise calculate the height from the ratio and the width
  17869. height = width * ratioMultiplier;
  17870. } // Ensure the CSS class is valid by starting with an alpha character
  17871. if (/^[^a-zA-Z]/.test(this.id())) {
  17872. idClass = 'dimensions-' + this.id();
  17873. } else {
  17874. idClass = this.id() + '-dimensions';
  17875. } // Ensure the right class is still on the player for the style element
  17876. this.addClass(idClass);
  17877. 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 ");
  17878. }
  17879. /**
  17880. * Load/Create an instance of playback {@link Tech} including element
  17881. * and API methods. Then append the `Tech` element in `Player` as a child.
  17882. *
  17883. * @param {string} techName
  17884. * name of the playback technology
  17885. *
  17886. * @param {string} source
  17887. * video source
  17888. *
  17889. * @private
  17890. */
  17891. ;
  17892. _proto.loadTech_ = function loadTech_(techName, source) {
  17893. var _this3 = this;
  17894. // Pause and remove current playback technology
  17895. if (this.tech_) {
  17896. this.unloadTech_();
  17897. }
  17898. var titleTechName = toTitleCase(techName);
  17899. var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
  17900. if (titleTechName !== 'Html5' && this.tag) {
  17901. Tech.getTech('Html5').disposeMediaElement(this.tag);
  17902. this.tag.player = null;
  17903. this.tag = null;
  17904. }
  17905. this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
  17906. this.isReady_ = false; // if autoplay is a string we pass false to the tech
  17907. // because the player is going to handle autoplay on `loadstart`
  17908. var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use.
  17909. var techOptions = {
  17910. source: source,
  17911. autoplay: autoplay,
  17912. 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
  17913. 'playerId': this.id(),
  17914. 'techId': this.id() + "_" + camelTechName + "_api",
  17915. 'playsinline': this.options_.playsinline,
  17916. 'preload': this.options_.preload,
  17917. 'loop': this.options_.loop,
  17918. 'muted': this.options_.muted,
  17919. 'poster': this.poster(),
  17920. 'language': this.language(),
  17921. 'playerElIngest': this.playerElIngest_ || false,
  17922. 'vtt.js': this.options_['vtt.js'],
  17923. 'canOverridePoster': !!this.options_.techCanOverridePoster,
  17924. 'enableSourceset': this.options_.enableSourceset
  17925. };
  17926. ALL.names.forEach(function (name$$1) {
  17927. var props = ALL[name$$1];
  17928. techOptions[props.getterName] = _this3[props.privateName];
  17929. });
  17930. assign(techOptions, this.options_[titleTechName]);
  17931. assign(techOptions, this.options_[camelTechName]);
  17932. assign(techOptions, this.options_[techName.toLowerCase()]);
  17933. if (this.tag) {
  17934. techOptions.tag = this.tag;
  17935. }
  17936. if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
  17937. techOptions.startTime = this.cache_.currentTime;
  17938. } // Initialize tech instance
  17939. var TechClass = Tech.getTech(techName);
  17940. if (!TechClass) {
  17941. throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
  17942. }
  17943. this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
  17944. this.tech_.ready(bind(this, this.handleTechReady_), true);
  17945. textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
  17946. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  17947. _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]);
  17948. });
  17949. Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
  17950. _this3.on(_this3.tech_, event, function (eventObj) {
  17951. if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) {
  17952. _this3.queuedCallbacks_.push({
  17953. callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3),
  17954. event: eventObj
  17955. });
  17956. return;
  17957. }
  17958. _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
  17959. });
  17960. });
  17961. this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
  17962. this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
  17963. this.on(this.tech_, 'waiting', this.handleTechWaiting_);
  17964. this.on(this.tech_, 'ended', this.handleTechEnded_);
  17965. this.on(this.tech_, 'seeking', this.handleTechSeeking_);
  17966. this.on(this.tech_, 'play', this.handleTechPlay_);
  17967. this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
  17968. this.on(this.tech_, 'pause', this.handleTechPause_);
  17969. this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
  17970. this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
  17971. this.on(this.tech_, 'error', this.handleTechError_);
  17972. this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
  17973. this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
  17974. this.on(this.tech_, 'textdata', this.handleTechTextData_);
  17975. this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
  17976. this.usingNativeControls(this.techGet_('controls'));
  17977. if (this.controls() && !this.usingNativeControls()) {
  17978. this.addTechControlsListeners_();
  17979. } // Add the tech element in the DOM if it was not already there
  17980. // Make sure to not insert the original video element if using Html5
  17981. if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
  17982. prependTo(this.tech_.el(), this.el());
  17983. } // Get rid of the original video tag reference after the first tech is loaded
  17984. if (this.tag) {
  17985. this.tag.player = null;
  17986. this.tag = null;
  17987. }
  17988. }
  17989. /**
  17990. * Unload and dispose of the current playback {@link Tech}.
  17991. *
  17992. * @private
  17993. */
  17994. ;
  17995. _proto.unloadTech_ = function unloadTech_() {
  17996. var _this4 = this;
  17997. // Save the current text tracks so that we can reuse the same text tracks with the next tech
  17998. ALL.names.forEach(function (name$$1) {
  17999. var props = ALL[name$$1];
  18000. _this4[props.privateName] = _this4[props.getterName]();
  18001. });
  18002. this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
  18003. this.isReady_ = false;
  18004. this.tech_.dispose();
  18005. this.tech_ = false;
  18006. if (this.isPosterFromTech_) {
  18007. this.poster_ = '';
  18008. this.trigger('posterchange');
  18009. }
  18010. this.isPosterFromTech_ = false;
  18011. }
  18012. /**
  18013. * Return a reference to the current {@link Tech}.
  18014. * It will print a warning by default about the danger of using the tech directly
  18015. * but any argument that is passed in will silence the warning.
  18016. *
  18017. * @param {*} [safety]
  18018. * Anything passed in to silence the warning
  18019. *
  18020. * @return {Tech}
  18021. * The Tech
  18022. */
  18023. ;
  18024. _proto.tech = function tech(safety) {
  18025. if (safety === undefined) {
  18026. log.warn(tsml(_templateObject$2()));
  18027. }
  18028. return this.tech_;
  18029. }
  18030. /**
  18031. * Set up click and touch listeners for the playback element
  18032. *
  18033. * - On desktops: a click on the video itself will toggle playback
  18034. * - On mobile devices: a click on the video toggles controls
  18035. * which is done by toggling the user state between active and
  18036. * inactive
  18037. * - A tap can signal that a user has become active or has become inactive
  18038. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  18039. * quick tap should hide them again (signaling the user is in an inactive
  18040. * viewing state)
  18041. * - In addition to this, we still want the user to be considered inactive after
  18042. * a few seconds of inactivity.
  18043. *
  18044. * > Note: the only part of iOS interaction we can't mimic with this setup
  18045. * is a touch and hold on the video element counting as activity in order to
  18046. * keep the controls showing, but that shouldn't be an issue. A touch and hold
  18047. * on any controls will still keep the user active
  18048. *
  18049. * @private
  18050. */
  18051. ;
  18052. _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
  18053. // Make sure to remove all the previous listeners in case we are called multiple times.
  18054. this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
  18055. // trigger mousedown/up.
  18056. // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
  18057. // Any touch events are set to block the mousedown event from happening
  18058. this.on(this.tech_, 'mousedown', this.handleTechClick_);
  18059. this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
  18060. // so we'll check if the controls were already showing before reporting user
  18061. // activity
  18062. this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
  18063. this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
  18064. this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
  18065. // listener cancels out any reportedUserActivity when setting userActive(false)
  18066. this.on(this.tech_, 'tap', this.handleTechTap_);
  18067. }
  18068. /**
  18069. * Remove the listeners used for click and tap controls. This is needed for
  18070. * toggling to controls disabled, where a tap/touch should do nothing.
  18071. *
  18072. * @private
  18073. */
  18074. ;
  18075. _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
  18076. // We don't want to just use `this.off()` because there might be other needed
  18077. // listeners added by techs that extend this.
  18078. this.off(this.tech_, 'tap', this.handleTechTap_);
  18079. this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
  18080. this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
  18081. this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
  18082. this.off(this.tech_, 'mousedown', this.handleTechClick_);
  18083. this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
  18084. }
  18085. /**
  18086. * Player waits for the tech to be ready
  18087. *
  18088. * @private
  18089. */
  18090. ;
  18091. _proto.handleTechReady_ = function handleTechReady_() {
  18092. this.triggerReady(); // Keep the same volume as before
  18093. if (this.cache_.volume) {
  18094. this.techCall_('setVolume', this.cache_.volume);
  18095. } // Look if the tech found a higher resolution poster while loading
  18096. this.handleTechPosterChange_(); // Update the duration if available
  18097. this.handleTechDurationChange_();
  18098. }
  18099. /**
  18100. * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
  18101. * function will also trigger {@link Player#firstplay} if it is the first loadstart
  18102. * for a video.
  18103. *
  18104. * @fires Player#loadstart
  18105. * @fires Player#firstplay
  18106. * @listens Tech#loadstart
  18107. * @private
  18108. */
  18109. ;
  18110. _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
  18111. // TODO: Update to use `emptied` event instead. See #1277.
  18112. this.removeClass('vjs-ended');
  18113. this.removeClass('vjs-seeking'); // reset the error state
  18114. this.error(null); // Update the duration
  18115. this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
  18116. // The firstplay event relies on both the play and loadstart events
  18117. // which can happen in any order for a new source
  18118. if (!this.paused()) {
  18119. /**
  18120. * Fired when the user agent begins looking for media data
  18121. *
  18122. * @event Player#loadstart
  18123. * @type {EventTarget~Event}
  18124. */
  18125. this.trigger('loadstart');
  18126. this.trigger('firstplay');
  18127. } else {
  18128. // reset the hasStarted state
  18129. this.hasStarted(false);
  18130. this.trigger('loadstart');
  18131. } // autoplay happens after loadstart for the browser,
  18132. // so we mimic that behavior
  18133. this.manualAutoplay_(this.autoplay());
  18134. }
  18135. /**
  18136. * Handle autoplay string values, rather than the typical boolean
  18137. * values that should be handled by the tech. Note that this is not
  18138. * part of any specification. Valid values and what they do can be
  18139. * found on the autoplay getter at Player#autoplay()
  18140. */
  18141. ;
  18142. _proto.manualAutoplay_ = function manualAutoplay_(type) {
  18143. var _this5 = this;
  18144. if (!this.tech_ || typeof type !== 'string') {
  18145. return;
  18146. }
  18147. var muted = function muted() {
  18148. var previouslyMuted = _this5.muted();
  18149. _this5.muted(true);
  18150. var restoreMuted = function restoreMuted() {
  18151. _this5.muted(previouslyMuted);
  18152. }; // restore muted on play terminatation
  18153. _this5.playTerminatedQueue_.push(restoreMuted);
  18154. var mutedPromise = _this5.play();
  18155. if (!isPromise(mutedPromise)) {
  18156. return;
  18157. }
  18158. return mutedPromise.catch(restoreMuted);
  18159. };
  18160. var promise; // if muted defaults to true
  18161. // the only thing we can do is call play
  18162. if (type === 'any' && this.muted() !== true) {
  18163. promise = this.play();
  18164. if (isPromise(promise)) {
  18165. promise = promise.catch(muted);
  18166. }
  18167. } else if (type === 'muted' && this.muted() !== true) {
  18168. promise = muted();
  18169. } else {
  18170. promise = this.play();
  18171. }
  18172. if (!isPromise(promise)) {
  18173. return;
  18174. }
  18175. return promise.then(function () {
  18176. _this5.trigger({
  18177. type: 'autoplay-success',
  18178. autoplay: type
  18179. });
  18180. }).catch(function (e) {
  18181. _this5.trigger({
  18182. type: 'autoplay-failure',
  18183. autoplay: type
  18184. });
  18185. });
  18186. }
  18187. /**
  18188. * Update the internal source caches so that we return the correct source from
  18189. * `src()`, `currentSource()`, and `currentSources()`.
  18190. *
  18191. * > Note: `currentSources` will not be updated if the source that is passed in exists
  18192. * in the current `currentSources` cache.
  18193. *
  18194. *
  18195. * @param {Tech~SourceObject} srcObj
  18196. * A string or object source to update our caches to.
  18197. */
  18198. ;
  18199. _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
  18200. if (srcObj === void 0) {
  18201. srcObj = '';
  18202. }
  18203. var src = srcObj;
  18204. var type = '';
  18205. if (typeof src !== 'string') {
  18206. src = srcObj.src;
  18207. type = srcObj.type;
  18208. } // make sure all the caches are set to default values
  18209. // to prevent null checking
  18210. this.cache_.source = this.cache_.source || {};
  18211. this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
  18212. if (src && !type) {
  18213. type = findMimetype(this, src);
  18214. } // update `currentSource` cache always
  18215. this.cache_.source = mergeOptions({}, srcObj, {
  18216. src: src,
  18217. type: type
  18218. });
  18219. var matchingSources = this.cache_.sources.filter(function (s) {
  18220. return s.src && s.src === src;
  18221. });
  18222. var sourceElSources = [];
  18223. var sourceEls = this.$$('source');
  18224. var matchingSourceEls = [];
  18225. for (var i = 0; i < sourceEls.length; i++) {
  18226. var sourceObj = getAttributes(sourceEls[i]);
  18227. sourceElSources.push(sourceObj);
  18228. if (sourceObj.src && sourceObj.src === src) {
  18229. matchingSourceEls.push(sourceObj.src);
  18230. }
  18231. } // if we have matching source els but not matching sources
  18232. // the current source cache is not up to date
  18233. if (matchingSourceEls.length && !matchingSources.length) {
  18234. this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
  18235. // sources cache to the `currentSource` cache
  18236. } else if (!matchingSources.length) {
  18237. this.cache_.sources = [this.cache_.source];
  18238. } // update the tech `src` cache
  18239. this.cache_.src = src;
  18240. }
  18241. /**
  18242. * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
  18243. * causing the media element to reload.
  18244. *
  18245. * It will fire for the initial source and each subsequent source.
  18246. * This event is a custom event from Video.js and is triggered by the {@link Tech}.
  18247. *
  18248. * The event object for this event contains a `src` property that will contain the source
  18249. * that was available when the event was triggered. This is generally only necessary if Video.js
  18250. * is switching techs while the source was being changed.
  18251. *
  18252. * It is also fired when `load` is called on the player (or media element)
  18253. * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
  18254. * says that the resource selection algorithm needs to be aborted and restarted.
  18255. * In this case, it is very likely that the `src` property will be set to the
  18256. * empty string `""` to indicate we do not know what the source will be but
  18257. * that it is changing.
  18258. *
  18259. * *This event is currently still experimental and may change in minor releases.*
  18260. * __To use this, pass `enableSourceset` option to the player.__
  18261. *
  18262. * @event Player#sourceset
  18263. * @type {EventTarget~Event}
  18264. * @prop {string} src
  18265. * The source url available when the `sourceset` was triggered.
  18266. * It will be an empty string if we cannot know what the source is
  18267. * but know that the source will change.
  18268. */
  18269. /**
  18270. * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
  18271. *
  18272. * @fires Player#sourceset
  18273. * @listens Tech#sourceset
  18274. * @private
  18275. */
  18276. ;
  18277. _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
  18278. var _this6 = this;
  18279. // only update the source cache when the source
  18280. // was not updated using the player api
  18281. if (!this.changingSrc_) {
  18282. var updateSourceCaches = function updateSourceCaches(src) {
  18283. return _this6.updateSourceCaches_(src);
  18284. };
  18285. var playerSrc = this.currentSource().src;
  18286. var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
  18287. if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
  18288. // if both the tech source and the player source were updated we assume
  18289. // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
  18290. if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
  18291. updateSourceCaches = function updateSourceCaches() {};
  18292. }
  18293. } // update the source to the intial source right away
  18294. // in some cases this will be empty string
  18295. updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
  18296. // wait for a `loadstart` to update the cache to `currentSrc`.
  18297. // If a sourceset happens before a `loadstart`, we reset the state
  18298. // as this function will be called again.
  18299. if (!event.src) {
  18300. var updateCache = function updateCache(e) {
  18301. if (e.type !== 'sourceset') {
  18302. var techSrc = _this6.techGet('currentSrc');
  18303. _this6.lastSource_.tech = techSrc;
  18304. _this6.updateSourceCaches_(techSrc);
  18305. }
  18306. _this6.tech_.off(['sourceset', 'loadstart'], updateCache);
  18307. };
  18308. this.tech_.one(['sourceset', 'loadstart'], updateCache);
  18309. }
  18310. }
  18311. this.lastSource_ = {
  18312. player: this.currentSource().src,
  18313. tech: event.src
  18314. };
  18315. this.trigger({
  18316. src: event.src,
  18317. type: 'sourceset'
  18318. });
  18319. }
  18320. /**
  18321. * Add/remove the vjs-has-started class
  18322. *
  18323. * @fires Player#firstplay
  18324. *
  18325. * @param {boolean} request
  18326. * - true: adds the class
  18327. * - false: remove the class
  18328. *
  18329. * @return {boolean}
  18330. * the boolean value of hasStarted_
  18331. */
  18332. ;
  18333. _proto.hasStarted = function hasStarted(request) {
  18334. if (request === undefined) {
  18335. // act as getter, if we have no request to change
  18336. return this.hasStarted_;
  18337. }
  18338. if (request === this.hasStarted_) {
  18339. return;
  18340. }
  18341. this.hasStarted_ = request;
  18342. if (this.hasStarted_) {
  18343. this.addClass('vjs-has-started');
  18344. this.trigger('firstplay');
  18345. } else {
  18346. this.removeClass('vjs-has-started');
  18347. }
  18348. }
  18349. /**
  18350. * Fired whenever the media begins or resumes playback
  18351. *
  18352. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
  18353. * @fires Player#play
  18354. * @listens Tech#play
  18355. * @private
  18356. */
  18357. ;
  18358. _proto.handleTechPlay_ = function handleTechPlay_() {
  18359. this.removeClass('vjs-ended');
  18360. this.removeClass('vjs-paused');
  18361. this.addClass('vjs-playing'); // hide the poster when the user hits play
  18362. this.hasStarted(true);
  18363. /**
  18364. * Triggered whenever an {@link Tech#play} event happens. Indicates that
  18365. * playback has started or resumed.
  18366. *
  18367. * @event Player#play
  18368. * @type {EventTarget~Event}
  18369. */
  18370. this.trigger('play');
  18371. }
  18372. /**
  18373. * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
  18374. *
  18375. * If there were any events queued while the playback rate was zero, fire
  18376. * those events now.
  18377. *
  18378. * @private
  18379. * @method Player#handleTechRateChange_
  18380. * @fires Player#ratechange
  18381. * @listens Tech#ratechange
  18382. */
  18383. ;
  18384. _proto.handleTechRateChange_ = function handleTechRateChange_() {
  18385. if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
  18386. this.queuedCallbacks_.forEach(function (queued) {
  18387. return queued.callback(queued.event);
  18388. });
  18389. this.queuedCallbacks_ = [];
  18390. }
  18391. this.cache_.lastPlaybackRate = this.tech_.playbackRate();
  18392. /**
  18393. * Fires when the playing speed of the audio/video is changed
  18394. *
  18395. * @event Player#ratechange
  18396. * @type {event}
  18397. */
  18398. this.trigger('ratechange');
  18399. }
  18400. /**
  18401. * Retrigger the `waiting` event that was triggered by the {@link Tech}.
  18402. *
  18403. * @fires Player#waiting
  18404. * @listens Tech#waiting
  18405. * @private
  18406. */
  18407. ;
  18408. _proto.handleTechWaiting_ = function handleTechWaiting_() {
  18409. var _this7 = this;
  18410. this.addClass('vjs-waiting');
  18411. /**
  18412. * A readyState change on the DOM element has caused playback to stop.
  18413. *
  18414. * @event Player#waiting
  18415. * @type {EventTarget~Event}
  18416. */
  18417. this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
  18418. // premature removal of the waiting class, wait for the time to change.
  18419. var timeWhenWaiting = this.currentTime();
  18420. var timeUpdateListener = function timeUpdateListener() {
  18421. if (timeWhenWaiting !== _this7.currentTime()) {
  18422. _this7.removeClass('vjs-waiting');
  18423. _this7.off('timeupdate', timeUpdateListener);
  18424. }
  18425. };
  18426. this.on('timeupdate', timeUpdateListener);
  18427. }
  18428. /**
  18429. * Retrigger the `canplay` event that was triggered by the {@link Tech}.
  18430. * > Note: This is not consistent between browsers. See #1351
  18431. *
  18432. * @fires Player#canplay
  18433. * @listens Tech#canplay
  18434. * @private
  18435. */
  18436. ;
  18437. _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
  18438. this.removeClass('vjs-waiting');
  18439. /**
  18440. * The media has a readyState of HAVE_FUTURE_DATA or greater.
  18441. *
  18442. * @event Player#canplay
  18443. * @type {EventTarget~Event}
  18444. */
  18445. this.trigger('canplay');
  18446. }
  18447. /**
  18448. * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
  18449. *
  18450. * @fires Player#canplaythrough
  18451. * @listens Tech#canplaythrough
  18452. * @private
  18453. */
  18454. ;
  18455. _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
  18456. this.removeClass('vjs-waiting');
  18457. /**
  18458. * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
  18459. * entire media file can be played without buffering.
  18460. *
  18461. * @event Player#canplaythrough
  18462. * @type {EventTarget~Event}
  18463. */
  18464. this.trigger('canplaythrough');
  18465. }
  18466. /**
  18467. * Retrigger the `playing` event that was triggered by the {@link Tech}.
  18468. *
  18469. * @fires Player#playing
  18470. * @listens Tech#playing
  18471. * @private
  18472. */
  18473. ;
  18474. _proto.handleTechPlaying_ = function handleTechPlaying_() {
  18475. this.removeClass('vjs-waiting');
  18476. /**
  18477. * The media is no longer blocked from playback, and has started playing.
  18478. *
  18479. * @event Player#playing
  18480. * @type {EventTarget~Event}
  18481. */
  18482. this.trigger('playing');
  18483. }
  18484. /**
  18485. * Retrigger the `seeking` event that was triggered by the {@link Tech}.
  18486. *
  18487. * @fires Player#seeking
  18488. * @listens Tech#seeking
  18489. * @private
  18490. */
  18491. ;
  18492. _proto.handleTechSeeking_ = function handleTechSeeking_() {
  18493. this.addClass('vjs-seeking');
  18494. /**
  18495. * Fired whenever the player is jumping to a new time
  18496. *
  18497. * @event Player#seeking
  18498. * @type {EventTarget~Event}
  18499. */
  18500. this.trigger('seeking');
  18501. }
  18502. /**
  18503. * Retrigger the `seeked` event that was triggered by the {@link Tech}.
  18504. *
  18505. * @fires Player#seeked
  18506. * @listens Tech#seeked
  18507. * @private
  18508. */
  18509. ;
  18510. _proto.handleTechSeeked_ = function handleTechSeeked_() {
  18511. this.removeClass('vjs-seeking');
  18512. this.removeClass('vjs-ended');
  18513. /**
  18514. * Fired when the player has finished jumping to a new time
  18515. *
  18516. * @event Player#seeked
  18517. * @type {EventTarget~Event}
  18518. */
  18519. this.trigger('seeked');
  18520. }
  18521. /**
  18522. * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
  18523. *
  18524. * @fires Player#firstplay
  18525. * @listens Tech#firstplay
  18526. * @deprecated As of 6.0 firstplay event is deprecated.
  18527. * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
  18528. * @private
  18529. */
  18530. ;
  18531. _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
  18532. // If the first starttime attribute is specified
  18533. // then we will start at the given offset in seconds
  18534. if (this.options_.starttime) {
  18535. log.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
  18536. this.currentTime(this.options_.starttime);
  18537. }
  18538. this.addClass('vjs-has-started');
  18539. /**
  18540. * Fired the first time a video is played. Not part of the HLS spec, and this is
  18541. * probably not the best implementation yet, so use sparingly. If you don't have a
  18542. * reason to prevent playback, use `myPlayer.one('play');` instead.
  18543. *
  18544. * @event Player#firstplay
  18545. * @deprecated As of 6.0 firstplay event is deprecated.
  18546. * @type {EventTarget~Event}
  18547. */
  18548. this.trigger('firstplay');
  18549. }
  18550. /**
  18551. * Retrigger the `pause` event that was triggered by the {@link Tech}.
  18552. *
  18553. * @fires Player#pause
  18554. * @listens Tech#pause
  18555. * @private
  18556. */
  18557. ;
  18558. _proto.handleTechPause_ = function handleTechPause_() {
  18559. this.removeClass('vjs-playing');
  18560. this.addClass('vjs-paused');
  18561. /**
  18562. * Fired whenever the media has been paused
  18563. *
  18564. * @event Player#pause
  18565. * @type {EventTarget~Event}
  18566. */
  18567. this.trigger('pause');
  18568. }
  18569. /**
  18570. * Retrigger the `ended` event that was triggered by the {@link Tech}.
  18571. *
  18572. * @fires Player#ended
  18573. * @listens Tech#ended
  18574. * @private
  18575. */
  18576. ;
  18577. _proto.handleTechEnded_ = function handleTechEnded_() {
  18578. this.addClass('vjs-ended');
  18579. if (this.options_.loop) {
  18580. this.currentTime(0);
  18581. this.play();
  18582. } else if (!this.paused()) {
  18583. this.pause();
  18584. }
  18585. /**
  18586. * Fired when the end of the media resource is reached (currentTime == duration)
  18587. *
  18588. * @event Player#ended
  18589. * @type {EventTarget~Event}
  18590. */
  18591. this.trigger('ended');
  18592. }
  18593. /**
  18594. * Fired when the duration of the media resource is first known or changed
  18595. *
  18596. * @listens Tech#durationchange
  18597. * @private
  18598. */
  18599. ;
  18600. _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
  18601. this.duration(this.techGet_('duration'));
  18602. }
  18603. /**
  18604. * Handle a click on the media element to play/pause
  18605. *
  18606. * @param {EventTarget~Event} event
  18607. * the event that caused this function to trigger
  18608. *
  18609. * @listens Tech#mousedown
  18610. * @private
  18611. */
  18612. ;
  18613. _proto.handleTechClick_ = function handleTechClick_(event) {
  18614. if (!isSingleLeftClick(event)) {
  18615. return;
  18616. } // When controls are disabled a click should not toggle playback because
  18617. // the click is considered a control
  18618. if (!this.controls_) {
  18619. return;
  18620. }
  18621. if (this.paused()) {
  18622. silencePromise(this.play());
  18623. } else {
  18624. this.pause();
  18625. }
  18626. }
  18627. /**
  18628. * Handle a double-click on the media element to enter/exit fullscreen
  18629. *
  18630. * @param {EventTarget~Event} event
  18631. * the event that caused this function to trigger
  18632. *
  18633. * @listens Tech#dblclick
  18634. * @private
  18635. */
  18636. ;
  18637. _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
  18638. if (!this.controls_) {
  18639. return;
  18640. } // we do not want to toggle fullscreen state
  18641. // when double-clicking inside a control bar or a modal
  18642. var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
  18643. return el.contains(event.target);
  18644. });
  18645. if (!inAllowedEls) {
  18646. /*
  18647. * options.userActions.doubleClick
  18648. *
  18649. * If `undefined` or `true`, double-click toggles fullscreen if controls are present
  18650. * Set to `false` to disable double-click handling
  18651. * Set to a function to substitute an external double-click handler
  18652. */
  18653. if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
  18654. if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
  18655. this.options_.userActions.doubleClick.call(this, event);
  18656. } else if (this.isFullscreen()) {
  18657. this.exitFullscreen();
  18658. } else {
  18659. this.requestFullscreen();
  18660. }
  18661. }
  18662. }
  18663. }
  18664. /**
  18665. * Handle a tap on the media element. It will toggle the user
  18666. * activity state, which hides and shows the controls.
  18667. *
  18668. * @listens Tech#tap
  18669. * @private
  18670. */
  18671. ;
  18672. _proto.handleTechTap_ = function handleTechTap_() {
  18673. this.userActive(!this.userActive());
  18674. }
  18675. /**
  18676. * Handle touch to start
  18677. *
  18678. * @listens Tech#touchstart
  18679. * @private
  18680. */
  18681. ;
  18682. _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
  18683. this.userWasActive = this.userActive();
  18684. }
  18685. /**
  18686. * Handle touch to move
  18687. *
  18688. * @listens Tech#touchmove
  18689. * @private
  18690. */
  18691. ;
  18692. _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
  18693. if (this.userWasActive) {
  18694. this.reportUserActivity();
  18695. }
  18696. }
  18697. /**
  18698. * Handle touch to end
  18699. *
  18700. * @param {EventTarget~Event} event
  18701. * the touchend event that triggered
  18702. * this function
  18703. *
  18704. * @listens Tech#touchend
  18705. * @private
  18706. */
  18707. ;
  18708. _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
  18709. // Stop the mouse events from also happening
  18710. event.preventDefault();
  18711. }
  18712. /**
  18713. * native click events on the SWF aren't triggered on IE11, Win8.1RT
  18714. * use stageclick events triggered from inside the SWF instead
  18715. *
  18716. * @private
  18717. * @listens stageclick
  18718. */
  18719. ;
  18720. _proto.handleStageClick_ = function handleStageClick_() {
  18721. this.reportUserActivity();
  18722. }
  18723. /**
  18724. * @private
  18725. */
  18726. ;
  18727. _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
  18728. if (this.isFullscreen()) {
  18729. this.addClass('vjs-fullscreen');
  18730. } else {
  18731. this.removeClass('vjs-fullscreen');
  18732. }
  18733. }
  18734. /**
  18735. * when the document fschange event triggers it calls this
  18736. */
  18737. ;
  18738. _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
  18739. var fsApi = FullscreenApi;
  18740. this.isFullscreen(document[fsApi.fullscreenElement] === this.el() || this.el().matches(':' + fsApi.fullscreen)); // If cancelling fullscreen, remove event listener.
  18741. if (this.isFullscreen() === false) {
  18742. off(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  18743. }
  18744. if (!prefixedAPI) {
  18745. /**
  18746. * @event Player#fullscreenchange
  18747. * @type {EventTarget~Event}
  18748. */
  18749. this.trigger('fullscreenchange');
  18750. }
  18751. }
  18752. /**
  18753. * Handle Tech Fullscreen Change
  18754. *
  18755. * @param {EventTarget~Event} event
  18756. * the fullscreenchange event that triggered this function
  18757. *
  18758. * @param {Object} data
  18759. * the data that was sent with the event
  18760. *
  18761. * @private
  18762. * @listens Tech#fullscreenchange
  18763. * @fires Player#fullscreenchange
  18764. */
  18765. ;
  18766. _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
  18767. if (data) {
  18768. this.isFullscreen(data.isFullscreen);
  18769. }
  18770. /**
  18771. * Fired when going in and out of fullscreen.
  18772. *
  18773. * @event Player#fullscreenchange
  18774. * @type {EventTarget~Event}
  18775. */
  18776. this.trigger('fullscreenchange');
  18777. }
  18778. /**
  18779. * Fires when an error occurred during the loading of an audio/video.
  18780. *
  18781. * @private
  18782. * @listens Tech#error
  18783. */
  18784. ;
  18785. _proto.handleTechError_ = function handleTechError_() {
  18786. var error = this.tech_.error();
  18787. this.error(error);
  18788. }
  18789. /**
  18790. * Retrigger the `textdata` event that was triggered by the {@link Tech}.
  18791. *
  18792. * @fires Player#textdata
  18793. * @listens Tech#textdata
  18794. * @private
  18795. */
  18796. ;
  18797. _proto.handleTechTextData_ = function handleTechTextData_() {
  18798. var data = null;
  18799. if (arguments.length > 1) {
  18800. data = arguments[1];
  18801. }
  18802. /**
  18803. * Fires when we get a textdata event from tech
  18804. *
  18805. * @event Player#textdata
  18806. * @type {EventTarget~Event}
  18807. */
  18808. this.trigger('textdata', data);
  18809. }
  18810. /**
  18811. * Get object for cached values.
  18812. *
  18813. * @return {Object}
  18814. * get the current object cache
  18815. */
  18816. ;
  18817. _proto.getCache = function getCache() {
  18818. return this.cache_;
  18819. }
  18820. /**
  18821. * Resets the internal cache object.
  18822. *
  18823. * Using this function outside the player constructor or reset method may
  18824. * have unintended side-effects.
  18825. *
  18826. * @private
  18827. */
  18828. ;
  18829. _proto.resetCache_ = function resetCache_() {
  18830. this.cache_ = {
  18831. // Right now, the currentTime is not _really_ cached because it is always
  18832. // retrieved from the tech (see: currentTime). However, for completeness,
  18833. // we set it to zero here to ensure that if we do start actually caching
  18834. // it, we reset it along with everything else.
  18835. currentTime: 0,
  18836. inactivityTimeout: this.options_.inactivityTimeout,
  18837. duration: NaN,
  18838. lastVolume: 1,
  18839. lastPlaybackRate: this.defaultPlaybackRate(),
  18840. media: null,
  18841. src: '',
  18842. source: {},
  18843. sources: [],
  18844. volume: 1
  18845. };
  18846. }
  18847. /**
  18848. * Pass values to the playback tech
  18849. *
  18850. * @param {string} [method]
  18851. * the method to call
  18852. *
  18853. * @param {Object} arg
  18854. * the argument to pass
  18855. *
  18856. * @private
  18857. */
  18858. ;
  18859. _proto.techCall_ = function techCall_(method, arg) {
  18860. // If it's not ready yet, call method when it is
  18861. this.ready(function () {
  18862. if (method in allowedSetters) {
  18863. return set$1(this.middleware_, this.tech_, method, arg);
  18864. } else if (method in allowedMediators) {
  18865. return mediate(this.middleware_, this.tech_, method, arg);
  18866. }
  18867. try {
  18868. if (this.tech_) {
  18869. this.tech_[method](arg);
  18870. }
  18871. } catch (e) {
  18872. log(e);
  18873. throw e;
  18874. }
  18875. }, true);
  18876. }
  18877. /**
  18878. * Get calls can't wait for the tech, and sometimes don't need to.
  18879. *
  18880. * @param {string} method
  18881. * Tech method
  18882. *
  18883. * @return {Function|undefined}
  18884. * the method or undefined
  18885. *
  18886. * @private
  18887. */
  18888. ;
  18889. _proto.techGet_ = function techGet_(method) {
  18890. if (!this.tech_ || !this.tech_.isReady_) {
  18891. return;
  18892. }
  18893. if (method in allowedGetters) {
  18894. return get(this.middleware_, this.tech_, method);
  18895. } else if (method in allowedMediators) {
  18896. return mediate(this.middleware_, this.tech_, method);
  18897. } // Flash likes to die and reload when you hide or reposition it.
  18898. // In these cases the object methods go away and we get errors.
  18899. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  18900. try {
  18901. return this.tech_[method]();
  18902. } catch (e) {
  18903. // When building additional tech libs, an expected method may not be defined yet
  18904. if (this.tech_[method] === undefined) {
  18905. log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
  18906. throw e;
  18907. } // When a method isn't available on the object it throws a TypeError
  18908. if (e.name === 'TypeError') {
  18909. log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
  18910. this.tech_.isReady_ = false;
  18911. throw e;
  18912. } // If error unknown, just log and throw
  18913. log(e);
  18914. throw e;
  18915. }
  18916. }
  18917. /**
  18918. * Attempt to begin playback at the first opportunity.
  18919. *
  18920. * @return {Promise|undefined}
  18921. * Returns a promise if the browser supports Promises (or one
  18922. * was passed in as an option). This promise will be resolved on
  18923. * the return value of play. If this is undefined it will fulfill the
  18924. * promise chain otherwise the promise chain will be fulfilled when
  18925. * the promise from play is fulfilled.
  18926. */
  18927. ;
  18928. _proto.play = function play() {
  18929. var _this8 = this;
  18930. var PromiseClass = this.options_.Promise || window$1.Promise;
  18931. if (PromiseClass) {
  18932. return new PromiseClass(function (resolve) {
  18933. _this8.play_(resolve);
  18934. });
  18935. }
  18936. return this.play_();
  18937. }
  18938. /**
  18939. * The actual logic for play, takes a callback that will be resolved on the
  18940. * return value of play. This allows us to resolve to the play promise if there
  18941. * is one on modern browsers.
  18942. *
  18943. * @private
  18944. * @param {Function} [callback]
  18945. * The callback that should be called when the techs play is actually called
  18946. */
  18947. ;
  18948. _proto.play_ = function play_(callback) {
  18949. var _this9 = this;
  18950. if (callback === void 0) {
  18951. callback = silencePromise;
  18952. }
  18953. this.playCallbacks_.push(callback);
  18954. var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
  18955. if (this.waitToPlay_) {
  18956. this.off(['ready', 'loadstart'], this.waitToPlay_);
  18957. this.waitToPlay_ = null;
  18958. } // if the player/tech is not ready or the src itself is not ready
  18959. // queue up a call to play on `ready` or `loadstart`
  18960. if (!this.isReady_ || !isSrcReady) {
  18961. this.waitToPlay_ = function (e) {
  18962. _this9.play_();
  18963. };
  18964. this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
  18965. // in that case, we need to prime the video element by calling load so it'll be ready in time
  18966. if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
  18967. this.load();
  18968. }
  18969. return;
  18970. } // If the player/tech is ready and we have a source, we can attempt playback.
  18971. var val = this.techGet_('play'); // play was terminated if the returned value is null
  18972. if (val === null) {
  18973. this.runPlayTerminatedQueue_();
  18974. } else {
  18975. this.runPlayCallbacks_(val);
  18976. }
  18977. }
  18978. /**
  18979. * These functions will be run when if play is terminated. If play
  18980. * runPlayCallbacks_ is run these function will not be run. This allows us
  18981. * to differenciate between a terminated play and an actual call to play.
  18982. */
  18983. ;
  18984. _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
  18985. var queue = this.playTerminatedQueue_.slice(0);
  18986. this.playTerminatedQueue_ = [];
  18987. queue.forEach(function (q) {
  18988. q();
  18989. });
  18990. }
  18991. /**
  18992. * When a callback to play is delayed we have to run these
  18993. * callbacks when play is actually called on the tech. This function
  18994. * runs the callbacks that were delayed and accepts the return value
  18995. * from the tech.
  18996. *
  18997. * @param {undefined|Promise} val
  18998. * The return value from the tech.
  18999. */
  19000. ;
  19001. _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
  19002. var callbacks = this.playCallbacks_.slice(0);
  19003. this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
  19004. this.playTerminatedQueue_ = [];
  19005. callbacks.forEach(function (cb) {
  19006. cb(val);
  19007. });
  19008. }
  19009. /**
  19010. * Pause the video playback
  19011. *
  19012. * @return {Player}
  19013. * A reference to the player object this function was called on
  19014. */
  19015. ;
  19016. _proto.pause = function pause() {
  19017. this.techCall_('pause');
  19018. }
  19019. /**
  19020. * Check if the player is paused or has yet to play
  19021. *
  19022. * @return {boolean}
  19023. * - false: if the media is currently playing
  19024. * - true: if media is not currently playing
  19025. */
  19026. ;
  19027. _proto.paused = function paused() {
  19028. // The initial state of paused should be true (in Safari it's actually false)
  19029. return this.techGet_('paused') === false ? false : true;
  19030. }
  19031. /**
  19032. * Get a TimeRange object representing the current ranges of time that the user
  19033. * has played.
  19034. *
  19035. * @return {TimeRange}
  19036. * A time range object that represents all the increments of time that have
  19037. * been played.
  19038. */
  19039. ;
  19040. _proto.played = function played() {
  19041. return this.techGet_('played') || createTimeRanges(0, 0);
  19042. }
  19043. /**
  19044. * Returns whether or not the user is "scrubbing". Scrubbing is
  19045. * when the user has clicked the progress bar handle and is
  19046. * dragging it along the progress bar.
  19047. *
  19048. * @param {boolean} [isScrubbing]
  19049. * whether the user is or is not scrubbing
  19050. *
  19051. * @return {boolean}
  19052. * The value of scrubbing when getting
  19053. */
  19054. ;
  19055. _proto.scrubbing = function scrubbing(isScrubbing) {
  19056. if (typeof isScrubbing === 'undefined') {
  19057. return this.scrubbing_;
  19058. }
  19059. this.scrubbing_ = !!isScrubbing;
  19060. if (isScrubbing) {
  19061. this.addClass('vjs-scrubbing');
  19062. } else {
  19063. this.removeClass('vjs-scrubbing');
  19064. }
  19065. }
  19066. /**
  19067. * Get or set the current time (in seconds)
  19068. *
  19069. * @param {number|string} [seconds]
  19070. * The time to seek to in seconds
  19071. *
  19072. * @return {number}
  19073. * - the current time in seconds when getting
  19074. */
  19075. ;
  19076. _proto.currentTime = function currentTime(seconds) {
  19077. if (typeof seconds !== 'undefined') {
  19078. if (seconds < 0) {
  19079. seconds = 0;
  19080. }
  19081. this.techCall_('setCurrentTime', seconds);
  19082. return;
  19083. } // cache last currentTime and return. default to 0 seconds
  19084. //
  19085. // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
  19086. // currentTime when scrubbing, but may not provide much performance benefit afterall.
  19087. // Should be tested. Also something has to read the actual current time or the cache will
  19088. // never get updated.
  19089. this.cache_.currentTime = this.techGet_('currentTime') || 0;
  19090. return this.cache_.currentTime;
  19091. }
  19092. /**
  19093. * Normally gets the length in time of the video in seconds;
  19094. * in all but the rarest use cases an argument will NOT be passed to the method
  19095. *
  19096. * > **NOTE**: The video must have started loading before the duration can be
  19097. * known, and in the case of Flash, may not be known until the video starts
  19098. * playing.
  19099. *
  19100. * @fires Player#durationchange
  19101. *
  19102. * @param {number} [seconds]
  19103. * The duration of the video to set in seconds
  19104. *
  19105. * @return {number}
  19106. * - The duration of the video in seconds when getting
  19107. */
  19108. ;
  19109. _proto.duration = function duration(seconds) {
  19110. if (seconds === undefined) {
  19111. // return NaN if the duration is not known
  19112. return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
  19113. }
  19114. seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
  19115. if (seconds < 0) {
  19116. seconds = Infinity;
  19117. }
  19118. if (seconds !== this.cache_.duration) {
  19119. // Cache the last set value for optimized scrubbing (esp. Flash)
  19120. this.cache_.duration = seconds;
  19121. if (seconds === Infinity) {
  19122. this.addClass('vjs-live');
  19123. if (this.options_.liveui && this.player_.liveTracker) {
  19124. this.addClass('vjs-liveui');
  19125. }
  19126. } else {
  19127. this.removeClass('vjs-live');
  19128. this.removeClass('vjs-liveui');
  19129. }
  19130. if (!isNaN(seconds)) {
  19131. // Do not fire durationchange unless the duration value is known.
  19132. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  19133. /**
  19134. * @event Player#durationchange
  19135. * @type {EventTarget~Event}
  19136. */
  19137. this.trigger('durationchange');
  19138. }
  19139. }
  19140. }
  19141. /**
  19142. * Calculates how much time is left in the video. Not part
  19143. * of the native video API.
  19144. *
  19145. * @return {number}
  19146. * The time remaining in seconds
  19147. */
  19148. ;
  19149. _proto.remainingTime = function remainingTime() {
  19150. return this.duration() - this.currentTime();
  19151. }
  19152. /**
  19153. * A remaining time function that is intented to be used when
  19154. * the time is to be displayed directly to the user.
  19155. *
  19156. * @return {number}
  19157. * The rounded time remaining in seconds
  19158. */
  19159. ;
  19160. _proto.remainingTimeDisplay = function remainingTimeDisplay() {
  19161. return Math.floor(this.duration()) - Math.floor(this.currentTime());
  19162. } //
  19163. // Kind of like an array of portions of the video that have been downloaded.
  19164. /**
  19165. * Get a TimeRange object with an array of the times of the video
  19166. * that have been downloaded. If you just want the percent of the
  19167. * video that's been downloaded, use bufferedPercent.
  19168. *
  19169. * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
  19170. *
  19171. * @return {TimeRange}
  19172. * A mock TimeRange object (following HTML spec)
  19173. */
  19174. ;
  19175. _proto.buffered = function buffered() {
  19176. var buffered = this.techGet_('buffered');
  19177. if (!buffered || !buffered.length) {
  19178. buffered = createTimeRanges(0, 0);
  19179. }
  19180. return buffered;
  19181. }
  19182. /**
  19183. * Get the percent (as a decimal) of the video that's been downloaded.
  19184. * This method is not a part of the native HTML video API.
  19185. *
  19186. * @return {number}
  19187. * A decimal between 0 and 1 representing the percent
  19188. * that is buffered 0 being 0% and 1 being 100%
  19189. */
  19190. ;
  19191. _proto.bufferedPercent = function bufferedPercent$$1() {
  19192. return bufferedPercent(this.buffered(), this.duration());
  19193. }
  19194. /**
  19195. * Get the ending time of the last buffered time range
  19196. * This is used in the progress bar to encapsulate all time ranges.
  19197. *
  19198. * @return {number}
  19199. * The end of the last buffered time range
  19200. */
  19201. ;
  19202. _proto.bufferedEnd = function bufferedEnd() {
  19203. var buffered = this.buffered();
  19204. var duration = this.duration();
  19205. var end = buffered.end(buffered.length - 1);
  19206. if (end > duration) {
  19207. end = duration;
  19208. }
  19209. return end;
  19210. }
  19211. /**
  19212. * Get or set the current volume of the media
  19213. *
  19214. * @param {number} [percentAsDecimal]
  19215. * The new volume as a decimal percent:
  19216. * - 0 is muted/0%/off
  19217. * - 1.0 is 100%/full
  19218. * - 0.5 is half volume or 50%
  19219. *
  19220. * @return {number}
  19221. * The current volume as a percent when getting
  19222. */
  19223. ;
  19224. _proto.volume = function volume(percentAsDecimal) {
  19225. var vol;
  19226. if (percentAsDecimal !== undefined) {
  19227. // Force value to between 0 and 1
  19228. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
  19229. this.cache_.volume = vol;
  19230. this.techCall_('setVolume', vol);
  19231. if (vol > 0) {
  19232. this.lastVolume_(vol);
  19233. }
  19234. return;
  19235. } // Default to 1 when returning current volume.
  19236. vol = parseFloat(this.techGet_('volume'));
  19237. return isNaN(vol) ? 1 : vol;
  19238. }
  19239. /**
  19240. * Get the current muted state, or turn mute on or off
  19241. *
  19242. * @param {boolean} [muted]
  19243. * - true to mute
  19244. * - false to unmute
  19245. *
  19246. * @return {boolean}
  19247. * - true if mute is on and getting
  19248. * - false if mute is off and getting
  19249. */
  19250. ;
  19251. _proto.muted = function muted(_muted) {
  19252. if (_muted !== undefined) {
  19253. this.techCall_('setMuted', _muted);
  19254. return;
  19255. }
  19256. return this.techGet_('muted') || false;
  19257. }
  19258. /**
  19259. * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
  19260. * indicates the state of muted on initial playback.
  19261. *
  19262. * ```js
  19263. * var myPlayer = videojs('some-player-id');
  19264. *
  19265. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  19266. *
  19267. * // get, should be false
  19268. * console.log(myPlayer.defaultMuted());
  19269. * // set to true
  19270. * myPlayer.defaultMuted(true);
  19271. * // get should be true
  19272. * console.log(myPlayer.defaultMuted());
  19273. * ```
  19274. *
  19275. * @param {boolean} [defaultMuted]
  19276. * - true to mute
  19277. * - false to unmute
  19278. *
  19279. * @return {boolean|Player}
  19280. * - true if defaultMuted is on and getting
  19281. * - false if defaultMuted is off and getting
  19282. * - A reference to the current player when setting
  19283. */
  19284. ;
  19285. _proto.defaultMuted = function defaultMuted(_defaultMuted) {
  19286. if (_defaultMuted !== undefined) {
  19287. return this.techCall_('setDefaultMuted', _defaultMuted);
  19288. }
  19289. return this.techGet_('defaultMuted') || false;
  19290. }
  19291. /**
  19292. * Get the last volume, or set it
  19293. *
  19294. * @param {number} [percentAsDecimal]
  19295. * The new last volume as a decimal percent:
  19296. * - 0 is muted/0%/off
  19297. * - 1.0 is 100%/full
  19298. * - 0.5 is half volume or 50%
  19299. *
  19300. * @return {number}
  19301. * the current value of lastVolume as a percent when getting
  19302. *
  19303. * @private
  19304. */
  19305. ;
  19306. _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
  19307. if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
  19308. this.cache_.lastVolume = percentAsDecimal;
  19309. return;
  19310. }
  19311. return this.cache_.lastVolume;
  19312. }
  19313. /**
  19314. * Check if current tech can support native fullscreen
  19315. * (e.g. with built in controls like iOS, so not our flash swf)
  19316. *
  19317. * @return {boolean}
  19318. * if native fullscreen is supported
  19319. */
  19320. ;
  19321. _proto.supportsFullScreen = function supportsFullScreen() {
  19322. return this.techGet_('supportsFullScreen') || false;
  19323. }
  19324. /**
  19325. * Check if the player is in fullscreen mode or tell the player that it
  19326. * is or is not in fullscreen mode.
  19327. *
  19328. * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
  19329. * property and instead document.fullscreenElement is used. But isFullscreen is
  19330. * still a valuable property for internal player workings.
  19331. *
  19332. * @param {boolean} [isFS]
  19333. * Set the players current fullscreen state
  19334. *
  19335. * @return {boolean}
  19336. * - true if fullscreen is on and getting
  19337. * - false if fullscreen is off and getting
  19338. */
  19339. ;
  19340. _proto.isFullscreen = function isFullscreen(isFS) {
  19341. if (isFS !== undefined) {
  19342. this.isFullscreen_ = !!isFS;
  19343. this.toggleFullscreenClass_();
  19344. return;
  19345. }
  19346. return !!this.isFullscreen_;
  19347. }
  19348. /**
  19349. * Increase the size of the video to full screen
  19350. * In some browsers, full screen is not supported natively, so it enters
  19351. * "full window mode", where the video fills the browser window.
  19352. * In browsers and devices that support native full screen, sometimes the
  19353. * browser's default controls will be shown, and not the Video.js custom skin.
  19354. * This includes most mobile devices (iOS, Android) and older versions of
  19355. * Safari.
  19356. *
  19357. * @fires Player#fullscreenchange
  19358. */
  19359. ;
  19360. _proto.requestFullscreen = function requestFullscreen() {
  19361. var fsApi = FullscreenApi;
  19362. this.isFullscreen(true);
  19363. if (fsApi.requestFullscreen) {
  19364. // the browser supports going fullscreen at the element level so we can
  19365. // take the controls fullscreen as well as the video
  19366. // Trigger fullscreenchange event after change
  19367. // We have to specifically add this each time, and remove
  19368. // when canceling fullscreen. Otherwise if there's multiple
  19369. // players on a page, they would all be reacting to the same fullscreen
  19370. // events
  19371. on(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  19372. silencePromise(this.el_[fsApi.requestFullscreen]());
  19373. } else if (this.tech_.supportsFullScreen()) {
  19374. // we can't take the video.js controls fullscreen but we can go fullscreen
  19375. // with native controls
  19376. this.techCall_('enterFullScreen');
  19377. } else {
  19378. // fullscreen isn't supported so we'll just stretch the video element to
  19379. // fill the viewport
  19380. this.enterFullWindow();
  19381. /**
  19382. * @event Player#fullscreenchange
  19383. * @type {EventTarget~Event}
  19384. */
  19385. this.trigger('fullscreenchange');
  19386. }
  19387. }
  19388. /**
  19389. * Return the video to its normal size after having been in full screen mode
  19390. *
  19391. * @fires Player#fullscreenchange
  19392. */
  19393. ;
  19394. _proto.exitFullscreen = function exitFullscreen() {
  19395. var fsApi = FullscreenApi;
  19396. this.isFullscreen(false); // Check for browser element fullscreen support
  19397. if (fsApi.requestFullscreen) {
  19398. silencePromise(document[fsApi.exitFullscreen]());
  19399. } else if (this.tech_.supportsFullScreen()) {
  19400. this.techCall_('exitFullScreen');
  19401. } else {
  19402. this.exitFullWindow();
  19403. /**
  19404. * @event Player#fullscreenchange
  19405. * @type {EventTarget~Event}
  19406. */
  19407. this.trigger('fullscreenchange');
  19408. }
  19409. }
  19410. /**
  19411. * When fullscreen isn't supported we can stretch the
  19412. * video container to as wide as the browser will let us.
  19413. *
  19414. * @fires Player#enterFullWindow
  19415. */
  19416. ;
  19417. _proto.enterFullWindow = function enterFullWindow() {
  19418. this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
  19419. this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
  19420. on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
  19421. document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
  19422. addClass(document.body, 'vjs-full-window');
  19423. /**
  19424. * @event Player#enterFullWindow
  19425. * @type {EventTarget~Event}
  19426. */
  19427. this.trigger('enterFullWindow');
  19428. }
  19429. /**
  19430. * Check for call to either exit full window or
  19431. * full screen on ESC key
  19432. *
  19433. * @param {string} event
  19434. * Event to check for key press
  19435. */
  19436. ;
  19437. _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
  19438. if (keycode.isEventKey(event, 'Esc')) {
  19439. if (this.isFullscreen() === true) {
  19440. this.exitFullscreen();
  19441. } else {
  19442. this.exitFullWindow();
  19443. }
  19444. }
  19445. }
  19446. /**
  19447. * Exit full window
  19448. *
  19449. * @fires Player#exitFullWindow
  19450. */
  19451. ;
  19452. _proto.exitFullWindow = function exitFullWindow() {
  19453. this.isFullWindow = false;
  19454. off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
  19455. document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
  19456. removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
  19457. // this.positionAll();
  19458. /**
  19459. * @event Player#exitFullWindow
  19460. * @type {EventTarget~Event}
  19461. */
  19462. this.trigger('exitFullWindow');
  19463. }
  19464. /**
  19465. * This gets called when a `Player` gains focus via a `focus` event.
  19466. * Turns on listening for `keydown` events. When they happen it
  19467. * calls `this.handleKeyPress`.
  19468. *
  19469. * @param {EventTarget~Event} event
  19470. * The `focus` event that caused this function to be called.
  19471. *
  19472. * @listens focus
  19473. */
  19474. ;
  19475. _proto.handleFocus = function handleFocus(event) {
  19476. // call off first to make sure we don't keep adding keydown handlers
  19477. off(document, 'keydown', this.boundHandleKeyPress_);
  19478. on(document, 'keydown', this.boundHandleKeyPress_);
  19479. }
  19480. /**
  19481. * Called when a `Player` loses focus. Turns off the listener for
  19482. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  19483. *
  19484. * @param {EventTarget~Event} event
  19485. * The `blur` event that caused this function to be called.
  19486. *
  19487. * @listens blur
  19488. */
  19489. ;
  19490. _proto.handleBlur = function handleBlur(event) {
  19491. off(document, 'keydown', this.boundHandleKeyPress_);
  19492. }
  19493. /**
  19494. * Called when this Player has focus and a key gets pressed down, or when
  19495. * any Component of this player receives a key press that it doesn't handle.
  19496. * This allows player-wide hotkeys (either as defined below, or optionally
  19497. * by an external function).
  19498. *
  19499. * @param {EventTarget~Event} event
  19500. * The `keydown` event that caused this function to be called.
  19501. *
  19502. * @listens keydown
  19503. */
  19504. ;
  19505. _proto.handleKeyPress = function handleKeyPress(event) {
  19506. if (this.options_.userActions && this.options_.userActions.hotkeys && this.options_.userActions.hotkeys !== false) {
  19507. if (typeof this.options_.userActions.hotkeys === 'function') {
  19508. this.options_.userActions.hotkeys.call(this, event);
  19509. } else {
  19510. this.handleHotkeys(event);
  19511. }
  19512. }
  19513. }
  19514. /**
  19515. * Called when this Player receives a hotkey keydown event.
  19516. * Supported player-wide hotkeys are:
  19517. *
  19518. * f - toggle fullscreen
  19519. * m - toggle mute
  19520. * k or Space - toggle play/pause
  19521. *
  19522. * @param {EventTarget~Event} event
  19523. * The `keydown` event that caused this function to be called.
  19524. */
  19525. ;
  19526. _proto.handleHotkeys = function handleHotkeys(event) {
  19527. var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
  19528. var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
  19529. fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
  19530. return keycode.isEventKey(keydownEvent, 'f');
  19531. } : _hotkeys$fullscreenKe,
  19532. _hotkeys$muteKey = hotkeys.muteKey,
  19533. muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
  19534. return keycode.isEventKey(keydownEvent, 'm');
  19535. } : _hotkeys$muteKey,
  19536. _hotkeys$playPauseKey = hotkeys.playPauseKey,
  19537. playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
  19538. return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
  19539. } : _hotkeys$playPauseKey;
  19540. if (fullscreenKey.call(this, event)) {
  19541. event.preventDefault();
  19542. var FSToggle = Component.getComponent('FullscreenToggle');
  19543. if (document[FullscreenApi.fullscreenEnabled] !== false) {
  19544. FSToggle.prototype.handleClick.call(this);
  19545. }
  19546. } else if (muteKey.call(this, event)) {
  19547. event.preventDefault();
  19548. var MuteToggle = Component.getComponent('MuteToggle');
  19549. MuteToggle.prototype.handleClick.call(this);
  19550. } else if (playPauseKey.call(this, event)) {
  19551. event.preventDefault();
  19552. var PlayToggle = Component.getComponent('PlayToggle');
  19553. PlayToggle.prototype.handleClick.call(this);
  19554. }
  19555. }
  19556. /**
  19557. * Check whether the player can play a given mimetype
  19558. *
  19559. * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
  19560. *
  19561. * @param {string} type
  19562. * The mimetype to check
  19563. *
  19564. * @return {string}
  19565. * 'probably', 'maybe', or '' (empty string)
  19566. */
  19567. ;
  19568. _proto.canPlayType = function canPlayType(type) {
  19569. var can; // Loop through each playback technology in the options order
  19570. for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
  19571. var techName = j[i];
  19572. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  19573. // Remove once that deprecated behavior is removed.
  19574. if (!tech) {
  19575. tech = Component.getComponent(techName);
  19576. } // Check if the current tech is defined before continuing
  19577. if (!tech) {
  19578. log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  19579. continue;
  19580. } // Check if the browser supports this technology
  19581. if (tech.isSupported()) {
  19582. can = tech.canPlayType(type);
  19583. if (can) {
  19584. return can;
  19585. }
  19586. }
  19587. }
  19588. return '';
  19589. }
  19590. /**
  19591. * Select source based on tech-order or source-order
  19592. * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
  19593. * defaults to tech-order selection
  19594. *
  19595. * @param {Array} sources
  19596. * The sources for a media asset
  19597. *
  19598. * @return {Object|boolean}
  19599. * Object of source and tech order or false
  19600. */
  19601. ;
  19602. _proto.selectSource = function selectSource(sources) {
  19603. var _this10 = this;
  19604. // Get only the techs specified in `techOrder` that exist and are supported by the
  19605. // current platform
  19606. var techs = this.options_.techOrder.map(function (techName) {
  19607. return [techName, Tech.getTech(techName)];
  19608. }).filter(function (_ref) {
  19609. var techName = _ref[0],
  19610. tech = _ref[1];
  19611. // Check if the current tech is defined before continuing
  19612. if (tech) {
  19613. // Check if the browser supports this technology
  19614. return tech.isSupported();
  19615. }
  19616. log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  19617. return false;
  19618. }); // Iterate over each `innerArray` element once per `outerArray` element and execute
  19619. // `tester` with both. If `tester` returns a non-falsy value, exit early and return
  19620. // that value.
  19621. var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
  19622. var found;
  19623. outerArray.some(function (outerChoice) {
  19624. return innerArray.some(function (innerChoice) {
  19625. found = tester(outerChoice, innerChoice);
  19626. if (found) {
  19627. return true;
  19628. }
  19629. });
  19630. });
  19631. return found;
  19632. };
  19633. var foundSourceAndTech;
  19634. var flip = function flip(fn) {
  19635. return function (a, b) {
  19636. return fn(b, a);
  19637. };
  19638. };
  19639. var finder = function finder(_ref2, source) {
  19640. var techName = _ref2[0],
  19641. tech = _ref2[1];
  19642. if (tech.canPlaySource(source, _this10.options_[techName.toLowerCase()])) {
  19643. return {
  19644. source: source,
  19645. tech: techName
  19646. };
  19647. }
  19648. }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
  19649. // to select from them based on their priority.
  19650. if (this.options_.sourceOrder) {
  19651. // Source-first ordering
  19652. foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
  19653. } else {
  19654. // Tech-first ordering
  19655. foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
  19656. }
  19657. return foundSourceAndTech || false;
  19658. }
  19659. /**
  19660. * Get or set the video source.
  19661. *
  19662. * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
  19663. * A SourceObject, an array of SourceObjects, or a string referencing
  19664. * a URL to a media source. It is _highly recommended_ that an object
  19665. * or array of objects is used here, so that source selection
  19666. * algorithms can take the `type` into account.
  19667. *
  19668. * If not provided, this method acts as a getter.
  19669. *
  19670. * @return {string|undefined}
  19671. * If the `source` argument is missing, returns the current source
  19672. * URL. Otherwise, returns nothing/undefined.
  19673. */
  19674. ;
  19675. _proto.src = function src(source) {
  19676. var _this11 = this;
  19677. // getter usage
  19678. if (typeof source === 'undefined') {
  19679. return this.cache_.src || '';
  19680. } // filter out invalid sources and turn our source into
  19681. // an array of source objects
  19682. var sources = filterSource(source); // if a source was passed in then it is invalid because
  19683. // it was filtered to a zero length Array. So we have to
  19684. // show an error
  19685. if (!sources.length) {
  19686. this.setTimeout(function () {
  19687. this.error({
  19688. code: 4,
  19689. message: this.localize(this.options_.notSupportedMessage)
  19690. });
  19691. }, 0);
  19692. return;
  19693. } // intial sources
  19694. this.changingSrc_ = true;
  19695. this.cache_.sources = sources;
  19696. this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
  19697. setSource(this, sources[0], function (middlewareSource, mws) {
  19698. _this11.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
  19699. // the source that is selected could be out of order from the cache update above this callback.
  19700. _this11.cache_.sources = sources;
  19701. _this11.updateSourceCaches_(middlewareSource);
  19702. var err = _this11.src_(middlewareSource);
  19703. if (err) {
  19704. if (sources.length > 1) {
  19705. return _this11.src(sources.slice(1));
  19706. }
  19707. _this11.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
  19708. _this11.setTimeout(function () {
  19709. this.error({
  19710. code: 4,
  19711. message: this.localize(this.options_.notSupportedMessage)
  19712. });
  19713. }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
  19714. // this needs a better comment about why this is needed
  19715. _this11.triggerReady();
  19716. return;
  19717. }
  19718. setTech(mws, _this11.tech_);
  19719. });
  19720. }
  19721. /**
  19722. * Set the source object on the tech, returns a boolean that indicates whether
  19723. * there is a tech that can play the source or not
  19724. *
  19725. * @param {Tech~SourceObject} source
  19726. * The source object to set on the Tech
  19727. *
  19728. * @return {boolean}
  19729. * - True if there is no Tech to playback this source
  19730. * - False otherwise
  19731. *
  19732. * @private
  19733. */
  19734. ;
  19735. _proto.src_ = function src_(source) {
  19736. var _this12 = this;
  19737. var sourceTech = this.selectSource([source]);
  19738. if (!sourceTech) {
  19739. return true;
  19740. }
  19741. if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
  19742. this.changingSrc_ = true; // load this technology with the chosen source
  19743. this.loadTech_(sourceTech.tech, sourceTech.source);
  19744. this.tech_.ready(function () {
  19745. _this12.changingSrc_ = false;
  19746. });
  19747. return false;
  19748. } // wait until the tech is ready to set the source
  19749. // and set it synchronously if possible (#2326)
  19750. this.ready(function () {
  19751. // The setSource tech method was added with source handlers
  19752. // so older techs won't support it
  19753. // We need to check the direct prototype for the case where subclasses
  19754. // of the tech do not support source handlers
  19755. if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
  19756. this.techCall_('setSource', source);
  19757. } else {
  19758. this.techCall_('src', source.src);
  19759. }
  19760. this.changingSrc_ = false;
  19761. }, true);
  19762. return false;
  19763. }
  19764. /**
  19765. * Begin loading the src data.
  19766. */
  19767. ;
  19768. _proto.load = function load() {
  19769. this.techCall_('load');
  19770. }
  19771. /**
  19772. * Reset the player. Loads the first tech in the techOrder,
  19773. * removes all the text tracks in the existing `tech`,
  19774. * and calls `reset` on the `tech`.
  19775. */
  19776. ;
  19777. _proto.reset = function reset() {
  19778. var _this13 = this;
  19779. var PromiseClass = this.options_.Promise || window$1.Promise;
  19780. if (this.paused() || !PromiseClass) {
  19781. this.doReset_();
  19782. } else {
  19783. var playPromise = this.play();
  19784. silencePromise(playPromise.then(function () {
  19785. return _this13.doReset_();
  19786. }));
  19787. }
  19788. };
  19789. _proto.doReset_ = function doReset_() {
  19790. if (this.tech_) {
  19791. this.tech_.clearTracks('text');
  19792. }
  19793. this.resetCache_();
  19794. this.poster('');
  19795. this.loadTech_(this.options_.techOrder[0], null);
  19796. this.techCall_('reset');
  19797. this.resetControlBarUI_();
  19798. if (isEvented(this)) {
  19799. this.trigger('playerreset');
  19800. }
  19801. }
  19802. /**
  19803. * Reset Control Bar's UI by calling sub-methods that reset
  19804. * all of Control Bar's components
  19805. */
  19806. ;
  19807. _proto.resetControlBarUI_ = function resetControlBarUI_() {
  19808. this.resetProgressBar_();
  19809. this.resetPlaybackRate_();
  19810. this.resetVolumeBar_();
  19811. }
  19812. /**
  19813. * Reset tech's progress so progress bar is reset in the UI
  19814. */
  19815. ;
  19816. _proto.resetProgressBar_ = function resetProgressBar_() {
  19817. this.currentTime(0);
  19818. var _this$controlBar = this.controlBar,
  19819. durationDisplay = _this$controlBar.durationDisplay,
  19820. remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
  19821. if (durationDisplay) {
  19822. durationDisplay.updateContent();
  19823. }
  19824. if (remainingTimeDisplay) {
  19825. remainingTimeDisplay.updateContent();
  19826. }
  19827. }
  19828. /**
  19829. * Reset Playback ratio
  19830. */
  19831. ;
  19832. _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
  19833. this.playbackRate(this.defaultPlaybackRate());
  19834. this.handleTechRateChange_();
  19835. }
  19836. /**
  19837. * Reset Volume bar
  19838. */
  19839. ;
  19840. _proto.resetVolumeBar_ = function resetVolumeBar_() {
  19841. this.volume(1.0);
  19842. this.trigger('volumechange');
  19843. }
  19844. /**
  19845. * Returns all of the current source objects.
  19846. *
  19847. * @return {Tech~SourceObject[]}
  19848. * The current source objects
  19849. */
  19850. ;
  19851. _proto.currentSources = function currentSources() {
  19852. var source = this.currentSource();
  19853. var sources = []; // assume `{}` or `{ src }`
  19854. if (Object.keys(source).length !== 0) {
  19855. sources.push(source);
  19856. }
  19857. return this.cache_.sources || sources;
  19858. }
  19859. /**
  19860. * Returns the current source object.
  19861. *
  19862. * @return {Tech~SourceObject}
  19863. * The current source object
  19864. */
  19865. ;
  19866. _proto.currentSource = function currentSource() {
  19867. return this.cache_.source || {};
  19868. }
  19869. /**
  19870. * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
  19871. * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
  19872. *
  19873. * @return {string}
  19874. * The current source
  19875. */
  19876. ;
  19877. _proto.currentSrc = function currentSrc() {
  19878. return this.currentSource() && this.currentSource().src || '';
  19879. }
  19880. /**
  19881. * Get the current source type e.g. video/mp4
  19882. * This can allow you rebuild the current source object so that you could load the same
  19883. * source and tech later
  19884. *
  19885. * @return {string}
  19886. * The source MIME type
  19887. */
  19888. ;
  19889. _proto.currentType = function currentType() {
  19890. return this.currentSource() && this.currentSource().type || '';
  19891. }
  19892. /**
  19893. * Get or set the preload attribute
  19894. *
  19895. * @param {boolean} [value]
  19896. * - true means that we should preload
  19897. * - false means that we should not preload
  19898. *
  19899. * @return {string}
  19900. * The preload attribute value when getting
  19901. */
  19902. ;
  19903. _proto.preload = function preload(value) {
  19904. if (value !== undefined) {
  19905. this.techCall_('setPreload', value);
  19906. this.options_.preload = value;
  19907. return;
  19908. }
  19909. return this.techGet_('preload');
  19910. }
  19911. /**
  19912. * Get or set the autoplay option. When this is a boolean it will
  19913. * modify the attribute on the tech. When this is a string the attribute on
  19914. * the tech will be removed and `Player` will handle autoplay on loadstarts.
  19915. *
  19916. * @param {boolean|string} [value]
  19917. * - true: autoplay using the browser behavior
  19918. * - false: do not autoplay
  19919. * - 'play': call play() on every loadstart
  19920. * - 'muted': call muted() then play() on every loadstart
  19921. * - 'any': call play() on every loadstart. if that fails call muted() then play().
  19922. * - *: values other than those listed here will be set `autoplay` to true
  19923. *
  19924. * @return {boolean|string}
  19925. * The current value of autoplay when getting
  19926. */
  19927. ;
  19928. _proto.autoplay = function autoplay(value) {
  19929. // getter usage
  19930. if (value === undefined) {
  19931. return this.options_.autoplay || false;
  19932. }
  19933. var techAutoplay; // if the value is a valid string set it to that
  19934. if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
  19935. this.options_.autoplay = value;
  19936. this.manualAutoplay_(value);
  19937. techAutoplay = false; // any falsy value sets autoplay to false in the browser,
  19938. // lets do the same
  19939. } else if (!value) {
  19940. this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
  19941. } else {
  19942. this.options_.autoplay = true;
  19943. }
  19944. techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
  19945. // a setAutoplay call on tech ready. We do this because the
  19946. // autoplay option will be passed in the constructor and we
  19947. // do not need to set it twice
  19948. if (this.tech_) {
  19949. this.techCall_('setAutoplay', techAutoplay);
  19950. }
  19951. }
  19952. /**
  19953. * Set or unset the playsinline attribute.
  19954. * Playsinline tells the browser that non-fullscreen playback is preferred.
  19955. *
  19956. * @param {boolean} [value]
  19957. * - true means that we should try to play inline by default
  19958. * - false means that we should use the browser's default playback mode,
  19959. * which in most cases is inline. iOS Safari is a notable exception
  19960. * and plays fullscreen by default.
  19961. *
  19962. * @return {string|Player}
  19963. * - the current value of playsinline
  19964. * - the player when setting
  19965. *
  19966. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  19967. */
  19968. ;
  19969. _proto.playsinline = function playsinline(value) {
  19970. if (value !== undefined) {
  19971. this.techCall_('setPlaysinline', value);
  19972. this.options_.playsinline = value;
  19973. return this;
  19974. }
  19975. return this.techGet_('playsinline');
  19976. }
  19977. /**
  19978. * Get or set the loop attribute on the video element.
  19979. *
  19980. * @param {boolean} [value]
  19981. * - true means that we should loop the video
  19982. * - false means that we should not loop the video
  19983. *
  19984. * @return {boolean}
  19985. * The current value of loop when getting
  19986. */
  19987. ;
  19988. _proto.loop = function loop(value) {
  19989. if (value !== undefined) {
  19990. this.techCall_('setLoop', value);
  19991. this.options_.loop = value;
  19992. return;
  19993. }
  19994. return this.techGet_('loop');
  19995. }
  19996. /**
  19997. * Get or set the poster image source url
  19998. *
  19999. * @fires Player#posterchange
  20000. *
  20001. * @param {string} [src]
  20002. * Poster image source URL
  20003. *
  20004. * @return {string}
  20005. * The current value of poster when getting
  20006. */
  20007. ;
  20008. _proto.poster = function poster(src) {
  20009. if (src === undefined) {
  20010. return this.poster_;
  20011. } // The correct way to remove a poster is to set as an empty string
  20012. // other falsey values will throw errors
  20013. if (!src) {
  20014. src = '';
  20015. }
  20016. if (src === this.poster_) {
  20017. return;
  20018. } // update the internal poster variable
  20019. this.poster_ = src; // update the tech's poster
  20020. this.techCall_('setPoster', src);
  20021. this.isPosterFromTech_ = false; // alert components that the poster has been set
  20022. /**
  20023. * This event fires when the poster image is changed on the player.
  20024. *
  20025. * @event Player#posterchange
  20026. * @type {EventTarget~Event}
  20027. */
  20028. this.trigger('posterchange');
  20029. }
  20030. /**
  20031. * Some techs (e.g. YouTube) can provide a poster source in an
  20032. * asynchronous way. We want the poster component to use this
  20033. * poster source so that it covers up the tech's controls.
  20034. * (YouTube's play button). However we only want to use this
  20035. * source if the player user hasn't set a poster through
  20036. * the normal APIs.
  20037. *
  20038. * @fires Player#posterchange
  20039. * @listens Tech#posterchange
  20040. * @private
  20041. */
  20042. ;
  20043. _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
  20044. if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
  20045. var newPoster = this.tech_.poster() || '';
  20046. if (newPoster !== this.poster_) {
  20047. this.poster_ = newPoster;
  20048. this.isPosterFromTech_ = true; // Let components know the poster has changed
  20049. this.trigger('posterchange');
  20050. }
  20051. }
  20052. }
  20053. /**
  20054. * Get or set whether or not the controls are showing.
  20055. *
  20056. * @fires Player#controlsenabled
  20057. *
  20058. * @param {boolean} [bool]
  20059. * - true to turn controls on
  20060. * - false to turn controls off
  20061. *
  20062. * @return {boolean}
  20063. * The current value of controls when getting
  20064. */
  20065. ;
  20066. _proto.controls = function controls(bool) {
  20067. if (bool === undefined) {
  20068. return !!this.controls_;
  20069. }
  20070. bool = !!bool; // Don't trigger a change event unless it actually changed
  20071. if (this.controls_ === bool) {
  20072. return;
  20073. }
  20074. this.controls_ = bool;
  20075. if (this.usingNativeControls()) {
  20076. this.techCall_('setControls', bool);
  20077. }
  20078. if (this.controls_) {
  20079. this.removeClass('vjs-controls-disabled');
  20080. this.addClass('vjs-controls-enabled');
  20081. /**
  20082. * @event Player#controlsenabled
  20083. * @type {EventTarget~Event}
  20084. */
  20085. this.trigger('controlsenabled');
  20086. if (!this.usingNativeControls()) {
  20087. this.addTechControlsListeners_();
  20088. }
  20089. } else {
  20090. this.removeClass('vjs-controls-enabled');
  20091. this.addClass('vjs-controls-disabled');
  20092. /**
  20093. * @event Player#controlsdisabled
  20094. * @type {EventTarget~Event}
  20095. */
  20096. this.trigger('controlsdisabled');
  20097. if (!this.usingNativeControls()) {
  20098. this.removeTechControlsListeners_();
  20099. }
  20100. }
  20101. }
  20102. /**
  20103. * Toggle native controls on/off. Native controls are the controls built into
  20104. * devices (e.g. default iPhone controls), Flash, or other techs
  20105. * (e.g. Vimeo Controls)
  20106. * **This should only be set by the current tech, because only the tech knows
  20107. * if it can support native controls**
  20108. *
  20109. * @fires Player#usingnativecontrols
  20110. * @fires Player#usingcustomcontrols
  20111. *
  20112. * @param {boolean} [bool]
  20113. * - true to turn native controls on
  20114. * - false to turn native controls off
  20115. *
  20116. * @return {boolean}
  20117. * The current value of native controls when getting
  20118. */
  20119. ;
  20120. _proto.usingNativeControls = function usingNativeControls(bool) {
  20121. if (bool === undefined) {
  20122. return !!this.usingNativeControls_;
  20123. }
  20124. bool = !!bool; // Don't trigger a change event unless it actually changed
  20125. if (this.usingNativeControls_ === bool) {
  20126. return;
  20127. }
  20128. this.usingNativeControls_ = bool;
  20129. if (this.usingNativeControls_) {
  20130. this.addClass('vjs-using-native-controls');
  20131. /**
  20132. * player is using the native device controls
  20133. *
  20134. * @event Player#usingnativecontrols
  20135. * @type {EventTarget~Event}
  20136. */
  20137. this.trigger('usingnativecontrols');
  20138. } else {
  20139. this.removeClass('vjs-using-native-controls');
  20140. /**
  20141. * player is using the custom HTML controls
  20142. *
  20143. * @event Player#usingcustomcontrols
  20144. * @type {EventTarget~Event}
  20145. */
  20146. this.trigger('usingcustomcontrols');
  20147. }
  20148. }
  20149. /**
  20150. * Set or get the current MediaError
  20151. *
  20152. * @fires Player#error
  20153. *
  20154. * @param {MediaError|string|number} [err]
  20155. * A MediaError or a string/number to be turned
  20156. * into a MediaError
  20157. *
  20158. * @return {MediaError|null}
  20159. * The current MediaError when getting (or null)
  20160. */
  20161. ;
  20162. _proto.error = function error(err) {
  20163. if (err === undefined) {
  20164. return this.error_ || null;
  20165. } // restoring to default
  20166. if (err === null) {
  20167. this.error_ = err;
  20168. this.removeClass('vjs-error');
  20169. if (this.errorDisplay) {
  20170. this.errorDisplay.close();
  20171. }
  20172. return;
  20173. }
  20174. this.error_ = new MediaError(err); // add the vjs-error classname to the player
  20175. this.addClass('vjs-error'); // log the name of the error type and any message
  20176. // IE11 logs "[object object]" and required you to expand message to see error object
  20177. log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
  20178. /**
  20179. * @event Player#error
  20180. * @type {EventTarget~Event}
  20181. */
  20182. this.trigger('error');
  20183. return;
  20184. }
  20185. /**
  20186. * Report user activity
  20187. *
  20188. * @param {Object} event
  20189. * Event object
  20190. */
  20191. ;
  20192. _proto.reportUserActivity = function reportUserActivity(event) {
  20193. this.userActivity_ = true;
  20194. }
  20195. /**
  20196. * Get/set if user is active
  20197. *
  20198. * @fires Player#useractive
  20199. * @fires Player#userinactive
  20200. *
  20201. * @param {boolean} [bool]
  20202. * - true if the user is active
  20203. * - false if the user is inactive
  20204. *
  20205. * @return {boolean}
  20206. * The current value of userActive when getting
  20207. */
  20208. ;
  20209. _proto.userActive = function userActive(bool) {
  20210. if (bool === undefined) {
  20211. return this.userActive_;
  20212. }
  20213. bool = !!bool;
  20214. if (bool === this.userActive_) {
  20215. return;
  20216. }
  20217. this.userActive_ = bool;
  20218. if (this.userActive_) {
  20219. this.userActivity_ = true;
  20220. this.removeClass('vjs-user-inactive');
  20221. this.addClass('vjs-user-active');
  20222. /**
  20223. * @event Player#useractive
  20224. * @type {EventTarget~Event}
  20225. */
  20226. this.trigger('useractive');
  20227. return;
  20228. } // Chrome/Safari/IE have bugs where when you change the cursor it can
  20229. // trigger a mousemove event. This causes an issue when you're hiding
  20230. // the cursor when the user is inactive, and a mousemove signals user
  20231. // activity. Making it impossible to go into inactive mode. Specifically
  20232. // this happens in fullscreen when we really need to hide the cursor.
  20233. //
  20234. // When this gets resolved in ALL browsers it can be removed
  20235. // https://code.google.com/p/chromium/issues/detail?id=103041
  20236. if (this.tech_) {
  20237. this.tech_.one('mousemove', function (e) {
  20238. e.stopPropagation();
  20239. e.preventDefault();
  20240. });
  20241. }
  20242. this.userActivity_ = false;
  20243. this.removeClass('vjs-user-active');
  20244. this.addClass('vjs-user-inactive');
  20245. /**
  20246. * @event Player#userinactive
  20247. * @type {EventTarget~Event}
  20248. */
  20249. this.trigger('userinactive');
  20250. }
  20251. /**
  20252. * Listen for user activity based on timeout value
  20253. *
  20254. * @private
  20255. */
  20256. ;
  20257. _proto.listenForUserActivity_ = function listenForUserActivity_() {
  20258. var mouseInProgress;
  20259. var lastMoveX;
  20260. var lastMoveY;
  20261. var handleActivity = bind(this, this.reportUserActivity);
  20262. var handleMouseMove = function handleMouseMove(e) {
  20263. // #1068 - Prevent mousemove spamming
  20264. // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
  20265. if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
  20266. lastMoveX = e.screenX;
  20267. lastMoveY = e.screenY;
  20268. handleActivity();
  20269. }
  20270. };
  20271. var handleMouseDown = function handleMouseDown() {
  20272. handleActivity(); // For as long as the they are touching the device or have their mouse down,
  20273. // we consider them active even if they're not moving their finger or mouse.
  20274. // So we want to continue to update that they are active
  20275. this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
  20276. // as the activityCheck interval (250) should ensure we never miss the
  20277. // next activityCheck
  20278. mouseInProgress = this.setInterval(handleActivity, 250);
  20279. };
  20280. var handleMouseUp = function handleMouseUp(event) {
  20281. handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
  20282. this.clearInterval(mouseInProgress);
  20283. }; // Any mouse movement will be considered user activity
  20284. this.on('mousedown', handleMouseDown);
  20285. this.on('mousemove', handleMouseMove);
  20286. this.on('mouseup', handleMouseUp);
  20287. var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
  20288. // controlBar would no longer be hidden by default timeout.
  20289. if (controlBar && !IS_IOS && !IS_ANDROID) {
  20290. controlBar.on('mouseenter', function (event) {
  20291. this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
  20292. this.player().options_.inactivityTimeout = 0;
  20293. });
  20294. controlBar.on('mouseleave', function (event) {
  20295. this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
  20296. });
  20297. } // Listen for keyboard navigation
  20298. // Shouldn't need to use inProgress interval because of key repeat
  20299. this.on('keydown', handleActivity);
  20300. this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
  20301. // the mousemove/touchmove function itself, to prevent performance degradation.
  20302. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  20303. // then gets picked up by this loop
  20304. // http://ejohn.org/blog/learning-from-twitter/
  20305. var inactivityTimeout;
  20306. this.setInterval(function () {
  20307. // Check to see if mouse/touch activity has happened
  20308. if (!this.userActivity_) {
  20309. return;
  20310. } // Reset the activity tracker
  20311. this.userActivity_ = false; // If the user state was inactive, set the state to active
  20312. this.userActive(true); // Clear any existing inactivity timeout to start the timer over
  20313. this.clearTimeout(inactivityTimeout);
  20314. var timeout = this.options_.inactivityTimeout;
  20315. if (timeout <= 0) {
  20316. return;
  20317. } // In <timeout> milliseconds, if no more activity has occurred the
  20318. // user will be considered inactive
  20319. inactivityTimeout = this.setTimeout(function () {
  20320. // Protect against the case where the inactivityTimeout can trigger just
  20321. // before the next user activity is picked up by the activity check loop
  20322. // causing a flicker
  20323. if (!this.userActivity_) {
  20324. this.userActive(false);
  20325. }
  20326. }, timeout);
  20327. }, 250);
  20328. }
  20329. /**
  20330. * Gets or sets the current playback rate. A playback rate of
  20331. * 1.0 represents normal speed and 0.5 would indicate half-speed
  20332. * playback, for instance.
  20333. *
  20334. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
  20335. *
  20336. * @param {number} [rate]
  20337. * New playback rate to set.
  20338. *
  20339. * @return {number}
  20340. * The current playback rate when getting or 1.0
  20341. */
  20342. ;
  20343. _proto.playbackRate = function playbackRate(rate) {
  20344. if (rate !== undefined) {
  20345. // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
  20346. // that is registered above
  20347. this.techCall_('setPlaybackRate', rate);
  20348. return;
  20349. }
  20350. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  20351. return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
  20352. }
  20353. return 1.0;
  20354. }
  20355. /**
  20356. * Gets or sets the current default playback rate. A default playback rate of
  20357. * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
  20358. * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
  20359. * not the current playbackRate.
  20360. *
  20361. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
  20362. *
  20363. * @param {number} [rate]
  20364. * New default playback rate to set.
  20365. *
  20366. * @return {number|Player}
  20367. * - The default playback rate when getting or 1.0
  20368. * - the player when setting
  20369. */
  20370. ;
  20371. _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
  20372. if (rate !== undefined) {
  20373. return this.techCall_('setDefaultPlaybackRate', rate);
  20374. }
  20375. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  20376. return this.techGet_('defaultPlaybackRate');
  20377. }
  20378. return 1.0;
  20379. }
  20380. /**
  20381. * Gets or sets the audio flag
  20382. *
  20383. * @param {boolean} bool
  20384. * - true signals that this is an audio player
  20385. * - false signals that this is not an audio player
  20386. *
  20387. * @return {boolean}
  20388. * The current value of isAudio when getting
  20389. */
  20390. ;
  20391. _proto.isAudio = function isAudio(bool) {
  20392. if (bool !== undefined) {
  20393. this.isAudio_ = !!bool;
  20394. return;
  20395. }
  20396. return !!this.isAudio_;
  20397. }
  20398. /**
  20399. * A helper method for adding a {@link TextTrack} to our
  20400. * {@link TextTrackList}.
  20401. *
  20402. * In addition to the W3C settings we allow adding additional info through options.
  20403. *
  20404. * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  20405. *
  20406. * @param {string} [kind]
  20407. * the kind of TextTrack you are adding
  20408. *
  20409. * @param {string} [label]
  20410. * the label to give the TextTrack label
  20411. *
  20412. * @param {string} [language]
  20413. * the language to set on the TextTrack
  20414. *
  20415. * @return {TextTrack|undefined}
  20416. * the TextTrack that was added or undefined
  20417. * if there is no tech
  20418. */
  20419. ;
  20420. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  20421. if (this.tech_) {
  20422. return this.tech_.addTextTrack(kind, label, language);
  20423. }
  20424. }
  20425. /**
  20426. * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
  20427. * automatically removed from the video element whenever the source changes, unless
  20428. * manualCleanup is set to false.
  20429. *
  20430. * @param {Object} options
  20431. * Options to pass to {@link HTMLTrackElement} during creation. See
  20432. * {@link HTMLTrackElement} for object properties that you should use.
  20433. *
  20434. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  20435. *
  20436. * @return {HtmlTrackElement}
  20437. * the HTMLTrackElement that was created and added
  20438. * to the HtmlTrackElementList and the remote
  20439. * TextTrackList
  20440. *
  20441. * @deprecated The default value of the "manualCleanup" parameter will default
  20442. * to "false" in upcoming versions of Video.js
  20443. */
  20444. ;
  20445. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  20446. if (this.tech_) {
  20447. return this.tech_.addRemoteTextTrack(options, manualCleanup);
  20448. }
  20449. }
  20450. /**
  20451. * Remove a remote {@link TextTrack} from the respective
  20452. * {@link TextTrackList} and {@link HtmlTrackElementList}.
  20453. *
  20454. * @param {Object} track
  20455. * Remote {@link TextTrack} to remove
  20456. *
  20457. * @return {undefined}
  20458. * does not return anything
  20459. */
  20460. ;
  20461. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
  20462. if (obj === void 0) {
  20463. obj = {};
  20464. }
  20465. var _obj = obj,
  20466. track = _obj.track;
  20467. if (!track) {
  20468. track = obj;
  20469. } // destructure the input into an object with a track argument, defaulting to arguments[0]
  20470. // default the whole argument to an empty object if nothing was passed in
  20471. if (this.tech_) {
  20472. return this.tech_.removeRemoteTextTrack(track);
  20473. }
  20474. }
  20475. /**
  20476. * Gets available media playback quality metrics as specified by the W3C's Media
  20477. * Playback Quality API.
  20478. *
  20479. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  20480. *
  20481. * @return {Object|undefined}
  20482. * An object with supported media playback quality metrics or undefined if there
  20483. * is no tech or the tech does not support it.
  20484. */
  20485. ;
  20486. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  20487. return this.techGet_('getVideoPlaybackQuality');
  20488. }
  20489. /**
  20490. * Get video width
  20491. *
  20492. * @return {number}
  20493. * current video width
  20494. */
  20495. ;
  20496. _proto.videoWidth = function videoWidth() {
  20497. return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
  20498. }
  20499. /**
  20500. * Get video height
  20501. *
  20502. * @return {number}
  20503. * current video height
  20504. */
  20505. ;
  20506. _proto.videoHeight = function videoHeight() {
  20507. return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
  20508. }
  20509. /**
  20510. * The player's language code
  20511. * NOTE: The language should be set in the player options if you want the
  20512. * the controls to be built with a specific language. Changing the language
  20513. * later will not update controls text.
  20514. *
  20515. * @param {string} [code]
  20516. * the language code to set the player to
  20517. *
  20518. * @return {string}
  20519. * The current language code when getting
  20520. */
  20521. ;
  20522. _proto.language = function language(code) {
  20523. if (code === undefined) {
  20524. return this.language_;
  20525. }
  20526. this.language_ = String(code).toLowerCase();
  20527. }
  20528. /**
  20529. * Get the player's language dictionary
  20530. * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
  20531. * Languages specified directly in the player options have precedence
  20532. *
  20533. * @return {Array}
  20534. * An array of of supported languages
  20535. */
  20536. ;
  20537. _proto.languages = function languages() {
  20538. return mergeOptions(Player.prototype.options_.languages, this.languages_);
  20539. }
  20540. /**
  20541. * returns a JavaScript object reperesenting the current track
  20542. * information. **DOES not return it as JSON**
  20543. *
  20544. * @return {Object}
  20545. * Object representing the current of track info
  20546. */
  20547. ;
  20548. _proto.toJSON = function toJSON() {
  20549. var options = mergeOptions(this.options_);
  20550. var tracks = options.tracks;
  20551. options.tracks = [];
  20552. for (var i = 0; i < tracks.length; i++) {
  20553. var track = tracks[i]; // deep merge tracks and null out player so no circular references
  20554. track = mergeOptions(track);
  20555. track.player = undefined;
  20556. options.tracks[i] = track;
  20557. }
  20558. return options;
  20559. }
  20560. /**
  20561. * Creates a simple modal dialog (an instance of the {@link ModalDialog}
  20562. * component) that immediately overlays the player with arbitrary
  20563. * content and removes itself when closed.
  20564. *
  20565. * @param {string|Function|Element|Array|null} content
  20566. * Same as {@link ModalDialog#content}'s param of the same name.
  20567. * The most straight-forward usage is to provide a string or DOM
  20568. * element.
  20569. *
  20570. * @param {Object} [options]
  20571. * Extra options which will be passed on to the {@link ModalDialog}.
  20572. *
  20573. * @return {ModalDialog}
  20574. * the {@link ModalDialog} that was created
  20575. */
  20576. ;
  20577. _proto.createModal = function createModal(content, options) {
  20578. var _this14 = this;
  20579. options = options || {};
  20580. options.content = content || '';
  20581. var modal = new ModalDialog(this, options);
  20582. this.addChild(modal);
  20583. modal.on('dispose', function () {
  20584. _this14.removeChild(modal);
  20585. });
  20586. modal.open();
  20587. return modal;
  20588. }
  20589. /**
  20590. * Change breakpoint classes when the player resizes.
  20591. *
  20592. * @private
  20593. */
  20594. ;
  20595. _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
  20596. if (!this.responsive()) {
  20597. return;
  20598. }
  20599. var currentBreakpoint = this.currentBreakpoint();
  20600. var currentWidth = this.currentWidth();
  20601. for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
  20602. var candidateBreakpoint = BREAKPOINT_ORDER[i];
  20603. var maxWidth = this.breakpoints_[candidateBreakpoint];
  20604. if (currentWidth <= maxWidth) {
  20605. // The current breakpoint did not change, nothing to do.
  20606. if (currentBreakpoint === candidateBreakpoint) {
  20607. return;
  20608. } // Only remove a class if there is a current breakpoint.
  20609. if (currentBreakpoint) {
  20610. this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
  20611. }
  20612. this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
  20613. this.breakpoint_ = candidateBreakpoint;
  20614. break;
  20615. }
  20616. }
  20617. }
  20618. /**
  20619. * Removes the current breakpoint.
  20620. *
  20621. * @private
  20622. */
  20623. ;
  20624. _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
  20625. var className = this.currentBreakpointClass();
  20626. this.breakpoint_ = '';
  20627. if (className) {
  20628. this.removeClass(className);
  20629. }
  20630. }
  20631. /**
  20632. * Get or set breakpoints on the player.
  20633. *
  20634. * Calling this method with an object or `true` will remove any previous
  20635. * custom breakpoints and start from the defaults again.
  20636. *
  20637. * @param {Object|boolean} [breakpoints]
  20638. * If an object is given, it can be used to provide custom
  20639. * breakpoints. If `true` is given, will set default breakpoints.
  20640. * If this argument is not given, will simply return the current
  20641. * breakpoints.
  20642. *
  20643. * @param {number} [breakpoints.tiny]
  20644. * The maximum width for the "vjs-layout-tiny" class.
  20645. *
  20646. * @param {number} [breakpoints.xsmall]
  20647. * The maximum width for the "vjs-layout-x-small" class.
  20648. *
  20649. * @param {number} [breakpoints.small]
  20650. * The maximum width for the "vjs-layout-small" class.
  20651. *
  20652. * @param {number} [breakpoints.medium]
  20653. * The maximum width for the "vjs-layout-medium" class.
  20654. *
  20655. * @param {number} [breakpoints.large]
  20656. * The maximum width for the "vjs-layout-large" class.
  20657. *
  20658. * @param {number} [breakpoints.xlarge]
  20659. * The maximum width for the "vjs-layout-x-large" class.
  20660. *
  20661. * @param {number} [breakpoints.huge]
  20662. * The maximum width for the "vjs-layout-huge" class.
  20663. *
  20664. * @return {Object}
  20665. * An object mapping breakpoint names to maximum width values.
  20666. */
  20667. ;
  20668. _proto.breakpoints = function breakpoints(_breakpoints) {
  20669. // Used as a getter.
  20670. if (_breakpoints === undefined) {
  20671. return assign(this.breakpoints_);
  20672. }
  20673. this.breakpoint_ = '';
  20674. this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
  20675. // selected breakpoint.
  20676. this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
  20677. return assign(this.breakpoints_);
  20678. }
  20679. /**
  20680. * Get or set a flag indicating whether or not this player should adjust
  20681. * its UI based on its dimensions.
  20682. *
  20683. * @param {boolean} value
  20684. * Should be `true` if the player should adjust its UI based on its
  20685. * dimensions; otherwise, should be `false`.
  20686. *
  20687. * @return {boolean}
  20688. * Will be `true` if this player should adjust its UI based on its
  20689. * dimensions; otherwise, will be `false`.
  20690. */
  20691. ;
  20692. _proto.responsive = function responsive(value) {
  20693. // Used as a getter.
  20694. if (value === undefined) {
  20695. return this.responsive_;
  20696. }
  20697. value = Boolean(value);
  20698. var current = this.responsive_; // Nothing changed.
  20699. if (value === current) {
  20700. return;
  20701. } // The value actually changed, set it.
  20702. this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
  20703. // player is now responsive.
  20704. if (value) {
  20705. this.on('playerresize', this.updateCurrentBreakpoint_);
  20706. this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
  20707. } else {
  20708. this.off('playerresize', this.updateCurrentBreakpoint_);
  20709. this.removeCurrentBreakpoint_();
  20710. }
  20711. return value;
  20712. }
  20713. /**
  20714. * Get current breakpoint name, if any.
  20715. *
  20716. * @return {string}
  20717. * If there is currently a breakpoint set, returns a the key from the
  20718. * breakpoints object matching it. Otherwise, returns an empty string.
  20719. */
  20720. ;
  20721. _proto.currentBreakpoint = function currentBreakpoint() {
  20722. return this.breakpoint_;
  20723. }
  20724. /**
  20725. * Get the current breakpoint class name.
  20726. *
  20727. * @return {string}
  20728. * The matching class name (e.g. `"vjs-layout-tiny"` or
  20729. * `"vjs-layout-large"`) for the current breakpoint. Empty string if
  20730. * there is no current breakpoint.
  20731. */
  20732. ;
  20733. _proto.currentBreakpointClass = function currentBreakpointClass() {
  20734. return BREAKPOINT_CLASSES[this.breakpoint_] || '';
  20735. }
  20736. /**
  20737. * An object that describes a single piece of media.
  20738. *
  20739. * Properties that are not part of this type description will be retained; so,
  20740. * this can be viewed as a generic metadata storage mechanism as well.
  20741. *
  20742. * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
  20743. * @typedef {Object} Player~MediaObject
  20744. *
  20745. * @property {string} [album]
  20746. * Unused, except if this object is passed to the `MediaSession`
  20747. * API.
  20748. *
  20749. * @property {string} [artist]
  20750. * Unused, except if this object is passed to the `MediaSession`
  20751. * API.
  20752. *
  20753. * @property {Object[]} [artwork]
  20754. * Unused, except if this object is passed to the `MediaSession`
  20755. * API. If not specified, will be populated via the `poster`, if
  20756. * available.
  20757. *
  20758. * @property {string} [poster]
  20759. * URL to an image that will display before playback.
  20760. *
  20761. * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
  20762. * A single source object, an array of source objects, or a string
  20763. * referencing a URL to a media source. It is _highly recommended_
  20764. * that an object or array of objects is used here, so that source
  20765. * selection algorithms can take the `type` into account.
  20766. *
  20767. * @property {string} [title]
  20768. * Unused, except if this object is passed to the `MediaSession`
  20769. * API.
  20770. *
  20771. * @property {Object[]} [textTracks]
  20772. * An array of objects to be used to create text tracks, following
  20773. * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
  20774. * For ease of removal, these will be created as "remote" text
  20775. * tracks and set to automatically clean up on source changes.
  20776. *
  20777. * These objects may have properties like `src`, `kind`, `label`,
  20778. * and `language`, see {@link Tech#createRemoteTextTrack}.
  20779. */
  20780. /**
  20781. * Populate the player using a {@link Player~MediaObject|MediaObject}.
  20782. *
  20783. * @param {Player~MediaObject} media
  20784. * A media object.
  20785. *
  20786. * @param {Function} ready
  20787. * A callback to be called when the player is ready.
  20788. */
  20789. ;
  20790. _proto.loadMedia = function loadMedia(media, ready) {
  20791. var _this15 = this;
  20792. if (!media || typeof media !== 'object') {
  20793. return;
  20794. }
  20795. this.reset(); // Clone the media object so it cannot be mutated from outside.
  20796. this.cache_.media = mergeOptions(media);
  20797. var _this$cache_$media = this.cache_.media,
  20798. artwork = _this$cache_$media.artwork,
  20799. poster = _this$cache_$media.poster,
  20800. src = _this$cache_$media.src,
  20801. textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
  20802. if (!artwork && poster) {
  20803. this.cache_.media.artwork = [{
  20804. src: poster,
  20805. type: getMimetype(poster)
  20806. }];
  20807. }
  20808. if (src) {
  20809. this.src(src);
  20810. }
  20811. if (poster) {
  20812. this.poster(poster);
  20813. }
  20814. if (Array.isArray(textTracks)) {
  20815. textTracks.forEach(function (tt) {
  20816. return _this15.addRemoteTextTrack(tt, false);
  20817. });
  20818. }
  20819. this.ready(ready);
  20820. }
  20821. /**
  20822. * Get a clone of the current {@link Player~MediaObject} for this player.
  20823. *
  20824. * If the `loadMedia` method has not been used, will attempt to return a
  20825. * {@link Player~MediaObject} based on the current state of the player.
  20826. *
  20827. * @return {Player~MediaObject}
  20828. */
  20829. ;
  20830. _proto.getMedia = function getMedia() {
  20831. if (!this.cache_.media) {
  20832. var poster = this.poster();
  20833. var src = this.currentSources();
  20834. var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
  20835. return {
  20836. kind: tt.kind,
  20837. label: tt.label,
  20838. language: tt.language,
  20839. src: tt.src
  20840. };
  20841. });
  20842. var media = {
  20843. src: src,
  20844. textTracks: textTracks
  20845. };
  20846. if (poster) {
  20847. media.poster = poster;
  20848. media.artwork = [{
  20849. src: media.poster,
  20850. type: getMimetype(media.poster)
  20851. }];
  20852. }
  20853. return media;
  20854. }
  20855. return mergeOptions(this.cache_.media);
  20856. }
  20857. /**
  20858. * Gets tag settings
  20859. *
  20860. * @param {Element} tag
  20861. * The player tag
  20862. *
  20863. * @return {Object}
  20864. * An object containing all of the settings
  20865. * for a player tag
  20866. */
  20867. ;
  20868. Player.getTagSettings = function getTagSettings(tag) {
  20869. var baseOptions = {
  20870. sources: [],
  20871. tracks: []
  20872. };
  20873. var tagOptions = getAttributes(tag);
  20874. var dataSetup = tagOptions['data-setup'];
  20875. if (hasClass(tag, 'vjs-fill')) {
  20876. tagOptions.fill = true;
  20877. }
  20878. if (hasClass(tag, 'vjs-fluid')) {
  20879. tagOptions.fluid = true;
  20880. } // Check if data-setup attr exists.
  20881. if (dataSetup !== null) {
  20882. // Parse options JSON
  20883. // If empty string, make it a parsable json object.
  20884. var _safeParseTuple = safeParseTuple(dataSetup || '{}'),
  20885. err = _safeParseTuple[0],
  20886. data = _safeParseTuple[1];
  20887. if (err) {
  20888. log.error(err);
  20889. }
  20890. assign(tagOptions, data);
  20891. }
  20892. assign(baseOptions, tagOptions); // Get tag children settings
  20893. if (tag.hasChildNodes()) {
  20894. var children = tag.childNodes;
  20895. for (var i = 0, j = children.length; i < j; i++) {
  20896. var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  20897. var childName = child.nodeName.toLowerCase();
  20898. if (childName === 'source') {
  20899. baseOptions.sources.push(getAttributes(child));
  20900. } else if (childName === 'track') {
  20901. baseOptions.tracks.push(getAttributes(child));
  20902. }
  20903. }
  20904. }
  20905. return baseOptions;
  20906. }
  20907. /**
  20908. * Determine whether or not flexbox is supported
  20909. *
  20910. * @return {boolean}
  20911. * - true if flexbox is supported
  20912. * - false if flexbox is not supported
  20913. */
  20914. ;
  20915. _proto.flexNotSupported_ = function flexNotSupported_() {
  20916. var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
  20917. // common flex features that we can rely on when checking for flex support.
  20918. 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
  20919. 'msFlexOrder' in elem.style);
  20920. };
  20921. return Player;
  20922. }(Component);
  20923. /**
  20924. * Get the {@link VideoTrackList}
  20925. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
  20926. *
  20927. * @return {VideoTrackList}
  20928. * the current video track list
  20929. *
  20930. * @method Player.prototype.videoTracks
  20931. */
  20932. /**
  20933. * Get the {@link AudioTrackList}
  20934. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
  20935. *
  20936. * @return {AudioTrackList}
  20937. * the current audio track list
  20938. *
  20939. * @method Player.prototype.audioTracks
  20940. */
  20941. /**
  20942. * Get the {@link TextTrackList}
  20943. *
  20944. * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  20945. *
  20946. * @return {TextTrackList}
  20947. * the current text track list
  20948. *
  20949. * @method Player.prototype.textTracks
  20950. */
  20951. /**
  20952. * Get the remote {@link TextTrackList}
  20953. *
  20954. * @return {TextTrackList}
  20955. * The current remote text track list
  20956. *
  20957. * @method Player.prototype.remoteTextTracks
  20958. */
  20959. /**
  20960. * Get the remote {@link HtmlTrackElementList} tracks.
  20961. *
  20962. * @return {HtmlTrackElementList}
  20963. * The current remote text track element list
  20964. *
  20965. * @method Player.prototype.remoteTextTrackEls
  20966. */
  20967. ALL.names.forEach(function (name$$1) {
  20968. var props = ALL[name$$1];
  20969. Player.prototype[props.getterName] = function () {
  20970. if (this.tech_) {
  20971. return this.tech_[props.getterName]();
  20972. } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
  20973. // these will be passed to the tech during loading
  20974. this[props.privateName] = this[props.privateName] || new props.ListClass();
  20975. return this[props.privateName];
  20976. };
  20977. });
  20978. /**
  20979. * Global enumeration of players.
  20980. *
  20981. * The keys are the player IDs and the values are either the {@link Player}
  20982. * instance or `null` for disposed players.
  20983. *
  20984. * @type {Object}
  20985. */
  20986. Player.players = {};
  20987. var navigator = window$1.navigator;
  20988. /*
  20989. * Player instance options, surfaced using options
  20990. * options = Player.prototype.options_
  20991. * Make changes in options, not here.
  20992. *
  20993. * @type {Object}
  20994. * @private
  20995. */
  20996. Player.prototype.options_ = {
  20997. // Default order of fallback technology
  20998. techOrder: Tech.defaultTechOrder_,
  20999. html5: {},
  21000. flash: {},
  21001. // default inactivity timeout
  21002. inactivityTimeout: 2000,
  21003. // default playback rates
  21004. playbackRates: [],
  21005. // Add playback rate selection by adding rates
  21006. // 'playbackRates': [0.5, 1, 1.5, 2],
  21007. liveui: false,
  21008. // Included control sets
  21009. children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
  21010. language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
  21011. // locales and their language translations
  21012. languages: {},
  21013. // Default message to show when a video cannot be played.
  21014. notSupportedMessage: 'No compatible source was found for this media.',
  21015. breakpoints: {},
  21016. responsive: false
  21017. };
  21018. [
  21019. /**
  21020. * Returns whether or not the player is in the "ended" state.
  21021. *
  21022. * @return {Boolean} True if the player is in the ended state, false if not.
  21023. * @method Player#ended
  21024. */
  21025. 'ended',
  21026. /**
  21027. * Returns whether or not the player is in the "seeking" state.
  21028. *
  21029. * @return {Boolean} True if the player is in the seeking state, false if not.
  21030. * @method Player#seeking
  21031. */
  21032. 'seeking',
  21033. /**
  21034. * Returns the TimeRanges of the media that are currently available
  21035. * for seeking to.
  21036. *
  21037. * @return {TimeRanges} the seekable intervals of the media timeline
  21038. * @method Player#seekable
  21039. */
  21040. 'seekable',
  21041. /**
  21042. * Returns the current state of network activity for the element, from
  21043. * the codes in the list below.
  21044. * - NETWORK_EMPTY (numeric value 0)
  21045. * The element has not yet been initialised. All attributes are in
  21046. * their initial states.
  21047. * - NETWORK_IDLE (numeric value 1)
  21048. * The element's resource selection algorithm is active and has
  21049. * selected a resource, but it is not actually using the network at
  21050. * this time.
  21051. * - NETWORK_LOADING (numeric value 2)
  21052. * The user agent is actively trying to download data.
  21053. * - NETWORK_NO_SOURCE (numeric value 3)
  21054. * The element's resource selection algorithm is active, but it has
  21055. * not yet found a resource to use.
  21056. *
  21057. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
  21058. * @return {number} the current network activity state
  21059. * @method Player#networkState
  21060. */
  21061. 'networkState',
  21062. /**
  21063. * Returns a value that expresses the current state of the element
  21064. * with respect to rendering the current playback position, from the
  21065. * codes in the list below.
  21066. * - HAVE_NOTHING (numeric value 0)
  21067. * No information regarding the media resource is available.
  21068. * - HAVE_METADATA (numeric value 1)
  21069. * Enough of the resource has been obtained that the duration of the
  21070. * resource is available.
  21071. * - HAVE_CURRENT_DATA (numeric value 2)
  21072. * Data for the immediate current playback position is available.
  21073. * - HAVE_FUTURE_DATA (numeric value 3)
  21074. * Data for the immediate current playback position is available, as
  21075. * well as enough data for the user agent to advance the current
  21076. * playback position in the direction of playback.
  21077. * - HAVE_ENOUGH_DATA (numeric value 4)
  21078. * The user agent estimates that enough data is available for
  21079. * playback to proceed uninterrupted.
  21080. *
  21081. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
  21082. * @return {number} the current playback rendering state
  21083. * @method Player#readyState
  21084. */
  21085. 'readyState'].forEach(function (fn) {
  21086. Player.prototype[fn] = function () {
  21087. return this.techGet_(fn);
  21088. };
  21089. });
  21090. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  21091. Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () {
  21092. return this.trigger(event);
  21093. };
  21094. });
  21095. /**
  21096. * Fired when the player has initial duration and dimension information
  21097. *
  21098. * @event Player#loadedmetadata
  21099. * @type {EventTarget~Event}
  21100. */
  21101. /**
  21102. * Fired when the player has downloaded data at the current playback position
  21103. *
  21104. * @event Player#loadeddata
  21105. * @type {EventTarget~Event}
  21106. */
  21107. /**
  21108. * Fired when the current playback position has changed *
  21109. * During playback this is fired every 15-250 milliseconds, depending on the
  21110. * playback technology in use.
  21111. *
  21112. * @event Player#timeupdate
  21113. * @type {EventTarget~Event}
  21114. */
  21115. /**
  21116. * Fired when the volume changes
  21117. *
  21118. * @event Player#volumechange
  21119. * @type {EventTarget~Event}
  21120. */
  21121. /**
  21122. * Reports whether or not a player has a plugin available.
  21123. *
  21124. * This does not report whether or not the plugin has ever been initialized
  21125. * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
  21126. *
  21127. * @method Player#hasPlugin
  21128. * @param {string} name
  21129. * The name of a plugin.
  21130. *
  21131. * @return {boolean}
  21132. * Whether or not this player has the requested plugin available.
  21133. */
  21134. /**
  21135. * Reports whether or not a player is using a plugin by name.
  21136. *
  21137. * For basic plugins, this only reports whether the plugin has _ever_ been
  21138. * initialized on this player.
  21139. *
  21140. * @method Player#usingPlugin
  21141. * @param {string} name
  21142. * The name of a plugin.
  21143. *
  21144. * @return {boolean}
  21145. * Whether or not this player is using the requested plugin.
  21146. */
  21147. Component.registerComponent('Player', Player);
  21148. /**
  21149. * The base plugin name.
  21150. *
  21151. * @private
  21152. * @constant
  21153. * @type {string}
  21154. */
  21155. var BASE_PLUGIN_NAME = 'plugin';
  21156. /**
  21157. * The key on which a player's active plugins cache is stored.
  21158. *
  21159. * @private
  21160. * @constant
  21161. * @type {string}
  21162. */
  21163. var PLUGIN_CACHE_KEY = 'activePlugins_';
  21164. /**
  21165. * Stores registered plugins in a private space.
  21166. *
  21167. * @private
  21168. * @type {Object}
  21169. */
  21170. var pluginStorage = {};
  21171. /**
  21172. * Reports whether or not a plugin has been registered.
  21173. *
  21174. * @private
  21175. * @param {string} name
  21176. * The name of a plugin.
  21177. *
  21178. * @return {boolean}
  21179. * Whether or not the plugin has been registered.
  21180. */
  21181. var pluginExists = function pluginExists(name) {
  21182. return pluginStorage.hasOwnProperty(name);
  21183. };
  21184. /**
  21185. * Get a single registered plugin by name.
  21186. *
  21187. * @private
  21188. * @param {string} name
  21189. * The name of a plugin.
  21190. *
  21191. * @return {Function|undefined}
  21192. * The plugin (or undefined).
  21193. */
  21194. var getPlugin = function getPlugin(name) {
  21195. return pluginExists(name) ? pluginStorage[name] : undefined;
  21196. };
  21197. /**
  21198. * Marks a plugin as "active" on a player.
  21199. *
  21200. * Also, ensures that the player has an object for tracking active plugins.
  21201. *
  21202. * @private
  21203. * @param {Player} player
  21204. * A Video.js player instance.
  21205. *
  21206. * @param {string} name
  21207. * The name of a plugin.
  21208. */
  21209. var markPluginAsActive = function markPluginAsActive(player, name) {
  21210. player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
  21211. player[PLUGIN_CACHE_KEY][name] = true;
  21212. };
  21213. /**
  21214. * Triggers a pair of plugin setup events.
  21215. *
  21216. * @private
  21217. * @param {Player} player
  21218. * A Video.js player instance.
  21219. *
  21220. * @param {Plugin~PluginEventHash} hash
  21221. * A plugin event hash.
  21222. *
  21223. * @param {boolean} [before]
  21224. * If true, prefixes the event name with "before". In other words,
  21225. * use this to trigger "beforepluginsetup" instead of "pluginsetup".
  21226. */
  21227. var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
  21228. var eventName = (before ? 'before' : '') + 'pluginsetup';
  21229. player.trigger(eventName, hash);
  21230. player.trigger(eventName + ':' + hash.name, hash);
  21231. };
  21232. /**
  21233. * Takes a basic plugin function and returns a wrapper function which marks
  21234. * on the player that the plugin has been activated.
  21235. *
  21236. * @private
  21237. * @param {string} name
  21238. * The name of the plugin.
  21239. *
  21240. * @param {Function} plugin
  21241. * The basic plugin.
  21242. *
  21243. * @return {Function}
  21244. * A wrapper function for the given plugin.
  21245. */
  21246. var createBasicPlugin = function createBasicPlugin(name, plugin) {
  21247. var basicPluginWrapper = function basicPluginWrapper() {
  21248. // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
  21249. // regardless, but we want the hash to be consistent with the hash provided
  21250. // for advanced plugins.
  21251. //
  21252. // The only potentially counter-intuitive thing here is the `instance` in
  21253. // the "pluginsetup" event is the value returned by the `plugin` function.
  21254. triggerSetupEvent(this, {
  21255. name: name,
  21256. plugin: plugin,
  21257. instance: null
  21258. }, true);
  21259. var instance = plugin.apply(this, arguments);
  21260. markPluginAsActive(this, name);
  21261. triggerSetupEvent(this, {
  21262. name: name,
  21263. plugin: plugin,
  21264. instance: instance
  21265. });
  21266. return instance;
  21267. };
  21268. Object.keys(plugin).forEach(function (prop) {
  21269. basicPluginWrapper[prop] = plugin[prop];
  21270. });
  21271. return basicPluginWrapper;
  21272. };
  21273. /**
  21274. * Takes a plugin sub-class and returns a factory function for generating
  21275. * instances of it.
  21276. *
  21277. * This factory function will replace itself with an instance of the requested
  21278. * sub-class of Plugin.
  21279. *
  21280. * @private
  21281. * @param {string} name
  21282. * The name of the plugin.
  21283. *
  21284. * @param {Plugin} PluginSubClass
  21285. * The advanced plugin.
  21286. *
  21287. * @return {Function}
  21288. */
  21289. var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
  21290. // Add a `name` property to the plugin prototype so that each plugin can
  21291. // refer to itself by name.
  21292. PluginSubClass.prototype.name = name;
  21293. return function () {
  21294. triggerSetupEvent(this, {
  21295. name: name,
  21296. plugin: PluginSubClass,
  21297. instance: null
  21298. }, true);
  21299. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  21300. args[_key] = arguments[_key];
  21301. }
  21302. var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
  21303. this[name] = function () {
  21304. return instance;
  21305. };
  21306. triggerSetupEvent(this, instance.getEventHash());
  21307. return instance;
  21308. };
  21309. };
  21310. /**
  21311. * Parent class for all advanced plugins.
  21312. *
  21313. * @mixes module:evented~EventedMixin
  21314. * @mixes module:stateful~StatefulMixin
  21315. * @fires Player#beforepluginsetup
  21316. * @fires Player#beforepluginsetup:$name
  21317. * @fires Player#pluginsetup
  21318. * @fires Player#pluginsetup:$name
  21319. * @listens Player#dispose
  21320. * @throws {Error}
  21321. * If attempting to instantiate the base {@link Plugin} class
  21322. * directly instead of via a sub-class.
  21323. */
  21324. var Plugin =
  21325. /*#__PURE__*/
  21326. function () {
  21327. /**
  21328. * Creates an instance of this class.
  21329. *
  21330. * Sub-classes should call `super` to ensure plugins are properly initialized.
  21331. *
  21332. * @param {Player} player
  21333. * A Video.js player instance.
  21334. */
  21335. function Plugin(player) {
  21336. if (this.constructor === Plugin) {
  21337. throw new Error('Plugin must be sub-classed; not directly instantiated.');
  21338. }
  21339. this.player = player; // Make this object evented, but remove the added `trigger` method so we
  21340. // use the prototype version instead.
  21341. evented(this);
  21342. delete this.trigger;
  21343. stateful(this, this.constructor.defaultState);
  21344. markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
  21345. // it later easily.
  21346. this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin.
  21347. player.on('dispose', this.dispose);
  21348. }
  21349. /**
  21350. * Get the version of the plugin that was set on <pluginName>.VERSION
  21351. */
  21352. var _proto = Plugin.prototype;
  21353. _proto.version = function version() {
  21354. return this.constructor.VERSION;
  21355. }
  21356. /**
  21357. * Each event triggered by plugins includes a hash of additional data with
  21358. * conventional properties.
  21359. *
  21360. * This returns that object or mutates an existing hash.
  21361. *
  21362. * @param {Object} [hash={}]
  21363. * An object to be used as event an event hash.
  21364. *
  21365. * @return {Plugin~PluginEventHash}
  21366. * An event hash object with provided properties mixed-in.
  21367. */
  21368. ;
  21369. _proto.getEventHash = function getEventHash(hash) {
  21370. if (hash === void 0) {
  21371. hash = {};
  21372. }
  21373. hash.name = this.name;
  21374. hash.plugin = this.constructor;
  21375. hash.instance = this;
  21376. return hash;
  21377. }
  21378. /**
  21379. * Triggers an event on the plugin object and overrides
  21380. * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
  21381. *
  21382. * @param {string|Object} event
  21383. * An event type or an object with a type property.
  21384. *
  21385. * @param {Object} [hash={}]
  21386. * Additional data hash to merge with a
  21387. * {@link Plugin~PluginEventHash|PluginEventHash}.
  21388. *
  21389. * @return {boolean}
  21390. * Whether or not default was prevented.
  21391. */
  21392. ;
  21393. _proto.trigger = function trigger$$1(event, hash) {
  21394. if (hash === void 0) {
  21395. hash = {};
  21396. }
  21397. return trigger(this.eventBusEl_, event, this.getEventHash(hash));
  21398. }
  21399. /**
  21400. * Handles "statechanged" events on the plugin. No-op by default, override by
  21401. * subclassing.
  21402. *
  21403. * @abstract
  21404. * @param {Event} e
  21405. * An event object provided by a "statechanged" event.
  21406. *
  21407. * @param {Object} e.changes
  21408. * An object describing changes that occurred with the "statechanged"
  21409. * event.
  21410. */
  21411. ;
  21412. _proto.handleStateChanged = function handleStateChanged(e) {}
  21413. /**
  21414. * Disposes a plugin.
  21415. *
  21416. * Subclasses can override this if they want, but for the sake of safety,
  21417. * it's probably best to subscribe the "dispose" event.
  21418. *
  21419. * @fires Plugin#dispose
  21420. */
  21421. ;
  21422. _proto.dispose = function dispose() {
  21423. var name = this.name,
  21424. player = this.player;
  21425. /**
  21426. * Signals that a advanced plugin is about to be disposed.
  21427. *
  21428. * @event Plugin#dispose
  21429. * @type {EventTarget~Event}
  21430. */
  21431. this.trigger('dispose');
  21432. this.off();
  21433. player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
  21434. // references between the player and the plugin instance and nulling out
  21435. // the plugin's state and replacing methods with a function that throws.
  21436. player[PLUGIN_CACHE_KEY][name] = false;
  21437. this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
  21438. // function, so that the plugin is ready to be set up again.
  21439. player[name] = createPluginFactory(name, pluginStorage[name]);
  21440. }
  21441. /**
  21442. * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
  21443. *
  21444. * @param {string|Function} plugin
  21445. * If a string, matches the name of a plugin. If a function, will be
  21446. * tested directly.
  21447. *
  21448. * @return {boolean}
  21449. * Whether or not a plugin is a basic plugin.
  21450. */
  21451. ;
  21452. Plugin.isBasic = function isBasic(plugin) {
  21453. var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
  21454. return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
  21455. }
  21456. /**
  21457. * Register a Video.js plugin.
  21458. *
  21459. * @param {string} name
  21460. * The name of the plugin to be registered. Must be a string and
  21461. * must not match an existing plugin or a method on the `Player`
  21462. * prototype.
  21463. *
  21464. * @param {Function} plugin
  21465. * A sub-class of `Plugin` or a function for basic plugins.
  21466. *
  21467. * @return {Function}
  21468. * For advanced plugins, a factory function for that plugin. For
  21469. * basic plugins, a wrapper function that initializes the plugin.
  21470. */
  21471. ;
  21472. Plugin.registerPlugin = function registerPlugin(name, plugin) {
  21473. if (typeof name !== 'string') {
  21474. throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
  21475. }
  21476. if (pluginExists(name)) {
  21477. log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
  21478. } else if (Player.prototype.hasOwnProperty(name)) {
  21479. throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
  21480. }
  21481. if (typeof plugin !== 'function') {
  21482. throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
  21483. }
  21484. pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
  21485. // the base Plugin class).
  21486. if (name !== BASE_PLUGIN_NAME) {
  21487. if (Plugin.isBasic(plugin)) {
  21488. Player.prototype[name] = createBasicPlugin(name, plugin);
  21489. } else {
  21490. Player.prototype[name] = createPluginFactory(name, plugin);
  21491. }
  21492. }
  21493. return plugin;
  21494. }
  21495. /**
  21496. * De-register a Video.js plugin.
  21497. *
  21498. * @param {string} name
  21499. * The name of the plugin to be de-registered. Must be a string that
  21500. * matches an existing plugin.
  21501. *
  21502. * @throws {Error}
  21503. * If an attempt is made to de-register the base plugin.
  21504. */
  21505. ;
  21506. Plugin.deregisterPlugin = function deregisterPlugin(name) {
  21507. if (name === BASE_PLUGIN_NAME) {
  21508. throw new Error('Cannot de-register base plugin.');
  21509. }
  21510. if (pluginExists(name)) {
  21511. delete pluginStorage[name];
  21512. delete Player.prototype[name];
  21513. }
  21514. }
  21515. /**
  21516. * Gets an object containing multiple Video.js plugins.
  21517. *
  21518. * @param {Array} [names]
  21519. * If provided, should be an array of plugin names. Defaults to _all_
  21520. * plugin names.
  21521. *
  21522. * @return {Object|undefined}
  21523. * An object containing plugin(s) associated with their name(s) or
  21524. * `undefined` if no matching plugins exist).
  21525. */
  21526. ;
  21527. Plugin.getPlugins = function getPlugins(names) {
  21528. if (names === void 0) {
  21529. names = Object.keys(pluginStorage);
  21530. }
  21531. var result;
  21532. names.forEach(function (name) {
  21533. var plugin = getPlugin(name);
  21534. if (plugin) {
  21535. result = result || {};
  21536. result[name] = plugin;
  21537. }
  21538. });
  21539. return result;
  21540. }
  21541. /**
  21542. * Gets a plugin's version, if available
  21543. *
  21544. * @param {string} name
  21545. * The name of a plugin.
  21546. *
  21547. * @return {string}
  21548. * The plugin's version or an empty string.
  21549. */
  21550. ;
  21551. Plugin.getPluginVersion = function getPluginVersion(name) {
  21552. var plugin = getPlugin(name);
  21553. return plugin && plugin.VERSION || '';
  21554. };
  21555. return Plugin;
  21556. }();
  21557. /**
  21558. * Gets a plugin by name if it exists.
  21559. *
  21560. * @static
  21561. * @method getPlugin
  21562. * @memberOf Plugin
  21563. * @param {string} name
  21564. * The name of a plugin.
  21565. *
  21566. * @returns {Function|undefined}
  21567. * The plugin (or `undefined`).
  21568. */
  21569. Plugin.getPlugin = getPlugin;
  21570. /**
  21571. * The name of the base plugin class as it is registered.
  21572. *
  21573. * @type {string}
  21574. */
  21575. Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
  21576. Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
  21577. /**
  21578. * Documented in player.js
  21579. *
  21580. * @ignore
  21581. */
  21582. Player.prototype.usingPlugin = function (name) {
  21583. return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
  21584. };
  21585. /**
  21586. * Documented in player.js
  21587. *
  21588. * @ignore
  21589. */
  21590. Player.prototype.hasPlugin = function (name) {
  21591. return !!pluginExists(name);
  21592. };
  21593. /**
  21594. * Signals that a plugin is about to be set up on a player.
  21595. *
  21596. * @event Player#beforepluginsetup
  21597. * @type {Plugin~PluginEventHash}
  21598. */
  21599. /**
  21600. * Signals that a plugin is about to be set up on a player - by name. The name
  21601. * is the name of the plugin.
  21602. *
  21603. * @event Player#beforepluginsetup:$name
  21604. * @type {Plugin~PluginEventHash}
  21605. */
  21606. /**
  21607. * Signals that a plugin has just been set up on a player.
  21608. *
  21609. * @event Player#pluginsetup
  21610. * @type {Plugin~PluginEventHash}
  21611. */
  21612. /**
  21613. * Signals that a plugin has just been set up on a player - by name. The name
  21614. * is the name of the plugin.
  21615. *
  21616. * @event Player#pluginsetup:$name
  21617. * @type {Plugin~PluginEventHash}
  21618. */
  21619. /**
  21620. * @typedef {Object} Plugin~PluginEventHash
  21621. *
  21622. * @property {string} instance
  21623. * For basic plugins, the return value of the plugin function. For
  21624. * advanced plugins, the plugin instance on which the event is fired.
  21625. *
  21626. * @property {string} name
  21627. * The name of the plugin.
  21628. *
  21629. * @property {string} plugin
  21630. * For basic plugins, the plugin function. For advanced plugins, the
  21631. * plugin class/constructor.
  21632. */
  21633. /**
  21634. * @file extend.js
  21635. * @module extend
  21636. */
  21637. /**
  21638. * A combination of node inherits and babel's inherits (after transpile).
  21639. * Both work the same but node adds `super_` to the subClass
  21640. * and Bable adds the superClass as __proto__. Both seem useful.
  21641. *
  21642. * @param {Object} subClass
  21643. * The class to inherit to
  21644. *
  21645. * @param {Object} superClass
  21646. * The class to inherit from
  21647. *
  21648. * @private
  21649. */
  21650. var _inherits$1 = function _inherits(subClass, superClass) {
  21651. if (typeof superClass !== 'function' && superClass !== null) {
  21652. throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  21653. }
  21654. subClass.prototype = Object.create(superClass && superClass.prototype, {
  21655. constructor: {
  21656. value: subClass,
  21657. enumerable: false,
  21658. writable: true,
  21659. configurable: true
  21660. }
  21661. });
  21662. if (superClass) {
  21663. // node
  21664. subClass.super_ = superClass;
  21665. }
  21666. };
  21667. /**
  21668. * Used to subclass an existing class by emulating ES subclassing using the
  21669. * `extends` keyword.
  21670. *
  21671. * @function
  21672. * @example
  21673. * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
  21674. * myCustomMethod: function() {
  21675. * // Do things in my method.
  21676. * }
  21677. * });
  21678. *
  21679. * @param {Function} superClass
  21680. * The class to inherit from
  21681. *
  21682. * @param {Object} [subClassMethods={}]
  21683. * Methods of the new class
  21684. *
  21685. * @return {Function}
  21686. * The new class with subClassMethods that inherited superClass.
  21687. */
  21688. var extend = function extend(superClass, subClassMethods) {
  21689. if (subClassMethods === void 0) {
  21690. subClassMethods = {};
  21691. }
  21692. var subClass = function subClass() {
  21693. superClass.apply(this, arguments);
  21694. };
  21695. var methods = {};
  21696. if (typeof subClassMethods === 'object') {
  21697. if (subClassMethods.constructor !== Object.prototype.constructor) {
  21698. subClass = subClassMethods.constructor;
  21699. }
  21700. methods = subClassMethods;
  21701. } else if (typeof subClassMethods === 'function') {
  21702. subClass = subClassMethods;
  21703. }
  21704. _inherits$1(subClass, superClass); // Extend subObj's prototype with functions and other properties from props
  21705. for (var name in methods) {
  21706. if (methods.hasOwnProperty(name)) {
  21707. subClass.prototype[name] = methods[name];
  21708. }
  21709. }
  21710. return subClass;
  21711. };
  21712. /**
  21713. * @file video.js
  21714. * @module videojs
  21715. */
  21716. /**
  21717. * Normalize an `id` value by trimming off a leading `#`
  21718. *
  21719. * @private
  21720. * @param {string} id
  21721. * A string, maybe with a leading `#`.
  21722. *
  21723. * @return {string}
  21724. * The string, without any leading `#`.
  21725. */
  21726. var normalizeId = function normalizeId(id) {
  21727. return id.indexOf('#') === 0 ? id.slice(1) : id;
  21728. };
  21729. /**
  21730. * The `videojs()` function doubles as the main function for users to create a
  21731. * {@link Player} instance as well as the main library namespace.
  21732. *
  21733. * It can also be used as a getter for a pre-existing {@link Player} instance.
  21734. * However, we _strongly_ recommend using `videojs.getPlayer()` for this
  21735. * purpose because it avoids any potential for unintended initialization.
  21736. *
  21737. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  21738. * of our JSDoc template, we cannot properly document this as both a function
  21739. * and a namespace, so its function signature is documented here.
  21740. *
  21741. * #### Arguments
  21742. * ##### id
  21743. * string|Element, **required**
  21744. *
  21745. * Video element or video element ID.
  21746. *
  21747. * ##### options
  21748. * Object, optional
  21749. *
  21750. * Options object for providing settings.
  21751. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  21752. *
  21753. * ##### ready
  21754. * {@link Component~ReadyCallback}, optional
  21755. *
  21756. * A function to be called when the {@link Player} and {@link Tech} are ready.
  21757. *
  21758. * #### Return Value
  21759. *
  21760. * The `videojs()` function returns a {@link Player} instance.
  21761. *
  21762. * @namespace
  21763. *
  21764. * @borrows AudioTrack as AudioTrack
  21765. * @borrows Component.getComponent as getComponent
  21766. * @borrows module:computed-style~computedStyle as computedStyle
  21767. * @borrows module:events.on as on
  21768. * @borrows module:events.one as one
  21769. * @borrows module:events.off as off
  21770. * @borrows module:events.trigger as trigger
  21771. * @borrows EventTarget as EventTarget
  21772. * @borrows module:extend~extend as extend
  21773. * @borrows module:fn.bind as bind
  21774. * @borrows module:format-time.formatTime as formatTime
  21775. * @borrows module:format-time.resetFormatTime as resetFormatTime
  21776. * @borrows module:format-time.setFormatTime as setFormatTime
  21777. * @borrows module:merge-options.mergeOptions as mergeOptions
  21778. * @borrows module:middleware.use as use
  21779. * @borrows Player.players as players
  21780. * @borrows Plugin.registerPlugin as registerPlugin
  21781. * @borrows Plugin.deregisterPlugin as deregisterPlugin
  21782. * @borrows Plugin.getPlugins as getPlugins
  21783. * @borrows Plugin.getPlugin as getPlugin
  21784. * @borrows Plugin.getPluginVersion as getPluginVersion
  21785. * @borrows Tech.getTech as getTech
  21786. * @borrows Tech.registerTech as registerTech
  21787. * @borrows TextTrack as TextTrack
  21788. * @borrows module:time-ranges.createTimeRanges as createTimeRange
  21789. * @borrows module:time-ranges.createTimeRanges as createTimeRanges
  21790. * @borrows module:url.isCrossOrigin as isCrossOrigin
  21791. * @borrows module:url.parseUrl as parseUrl
  21792. * @borrows VideoTrack as VideoTrack
  21793. *
  21794. * @param {string|Element} id
  21795. * Video element or video element ID.
  21796. *
  21797. * @param {Object} [options]
  21798. * Options object for providing settings.
  21799. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  21800. *
  21801. * @param {Component~ReadyCallback} [ready]
  21802. * A function to be called when the {@link Player} and {@link Tech} are
  21803. * ready.
  21804. *
  21805. * @return {Player}
  21806. * The `videojs()` function returns a {@link Player|Player} instance.
  21807. */
  21808. function videojs$1(id, options, ready) {
  21809. var player = videojs$1.getPlayer(id);
  21810. if (player) {
  21811. if (options) {
  21812. log.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
  21813. }
  21814. if (ready) {
  21815. player.ready(ready);
  21816. }
  21817. return player;
  21818. }
  21819. var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
  21820. if (!isEl(el)) {
  21821. throw new TypeError('The element or ID supplied is not valid. (videojs)');
  21822. } // document.body.contains(el) will only check if el is contained within that one document.
  21823. // This causes problems for elements in iframes.
  21824. // Instead, use the element's ownerDocument instead of the global document.
  21825. // This will make sure that the element is indeed in the dom of that document.
  21826. // Additionally, check that the document in question has a default view.
  21827. // If the document is no longer attached to the dom, the defaultView of the document will be null.
  21828. if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
  21829. log.warn('The element supplied is not included in the DOM');
  21830. }
  21831. options = options || {};
  21832. videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
  21833. var opts = hookFunction(el, mergeOptions(options));
  21834. if (!isObject(opts) || Array.isArray(opts)) {
  21835. log.error('please return an object in beforesetup hooks');
  21836. return;
  21837. }
  21838. options = mergeOptions(options, opts);
  21839. }); // We get the current "Player" component here in case an integration has
  21840. // replaced it with a custom player.
  21841. var PlayerComponent = Component.getComponent('Player');
  21842. player = new PlayerComponent(el, options, ready);
  21843. videojs$1.hooks('setup').forEach(function (hookFunction) {
  21844. return hookFunction(player);
  21845. });
  21846. return player;
  21847. }
  21848. /**
  21849. * An Object that contains lifecycle hooks as keys which point to an array
  21850. * of functions that are run when a lifecycle is triggered
  21851. *
  21852. * @private
  21853. */
  21854. videojs$1.hooks_ = {};
  21855. /**
  21856. * Get a list of hooks for a specific lifecycle
  21857. *
  21858. * @param {string} type
  21859. * the lifecyle to get hooks from
  21860. *
  21861. * @param {Function|Function[]} [fn]
  21862. * Optionally add a hook (or hooks) to the lifecycle that your are getting.
  21863. *
  21864. * @return {Array}
  21865. * an array of hooks, or an empty array if there are none.
  21866. */
  21867. videojs$1.hooks = function (type, fn) {
  21868. videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
  21869. if (fn) {
  21870. videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
  21871. }
  21872. return videojs$1.hooks_[type];
  21873. };
  21874. /**
  21875. * Add a function hook to a specific videojs lifecycle.
  21876. *
  21877. * @param {string} type
  21878. * the lifecycle to hook the function to.
  21879. *
  21880. * @param {Function|Function[]}
  21881. * The function or array of functions to attach.
  21882. */
  21883. videojs$1.hook = function (type, fn) {
  21884. videojs$1.hooks(type, fn);
  21885. };
  21886. /**
  21887. * Add a function hook that will only run once to a specific videojs lifecycle.
  21888. *
  21889. * @param {string} type
  21890. * the lifecycle to hook the function to.
  21891. *
  21892. * @param {Function|Function[]}
  21893. * The function or array of functions to attach.
  21894. */
  21895. videojs$1.hookOnce = function (type, fn) {
  21896. videojs$1.hooks(type, [].concat(fn).map(function (original) {
  21897. var wrapper = function wrapper() {
  21898. videojs$1.removeHook(type, wrapper);
  21899. return original.apply(void 0, arguments);
  21900. };
  21901. return wrapper;
  21902. }));
  21903. };
  21904. /**
  21905. * Remove a hook from a specific videojs lifecycle.
  21906. *
  21907. * @param {string} type
  21908. * the lifecycle that the function hooked to
  21909. *
  21910. * @param {Function} fn
  21911. * The hooked function to remove
  21912. *
  21913. * @return {boolean}
  21914. * The function that was removed or undef
  21915. */
  21916. videojs$1.removeHook = function (type, fn) {
  21917. var index = videojs$1.hooks(type).indexOf(fn);
  21918. if (index <= -1) {
  21919. return false;
  21920. }
  21921. videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
  21922. videojs$1.hooks_[type].splice(index, 1);
  21923. return true;
  21924. }; // Add default styles
  21925. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
  21926. var style$1 = $('.vjs-styles-defaults');
  21927. if (!style$1) {
  21928. style$1 = createStyleElement('vjs-styles-defaults');
  21929. var head = $('head');
  21930. if (head) {
  21931. head.insertBefore(style$1, head.firstChild);
  21932. }
  21933. setTextContent(style$1, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ");
  21934. }
  21935. } // Run Auto-load players
  21936. // You have to wait at least once in case this script is loaded after your
  21937. // video in the DOM (weird behavior only with minified version)
  21938. autoSetupTimeout(1, videojs$1);
  21939. /**
  21940. * Current Video.js version. Follows [semantic versioning](https://semver.org/).
  21941. *
  21942. * @type {string}
  21943. */
  21944. videojs$1.VERSION = version;
  21945. /**
  21946. * The global options object. These are the settings that take effect
  21947. * if no overrides are specified when the player is created.
  21948. *
  21949. * @type {Object}
  21950. */
  21951. videojs$1.options = Player.prototype.options_;
  21952. /**
  21953. * Get an object with the currently created players, keyed by player ID
  21954. *
  21955. * @return {Object}
  21956. * The created players
  21957. */
  21958. videojs$1.getPlayers = function () {
  21959. return Player.players;
  21960. };
  21961. /**
  21962. * Get a single player based on an ID or DOM element.
  21963. *
  21964. * This is useful if you want to check if an element or ID has an associated
  21965. * Video.js player, but not create one if it doesn't.
  21966. *
  21967. * @param {string|Element} id
  21968. * An HTML element - `<video>`, `<audio>`, or `<video-js>` -
  21969. * or a string matching the `id` of such an element.
  21970. *
  21971. * @return {Player|undefined}
  21972. * A player instance or `undefined` if there is no player instance
  21973. * matching the argument.
  21974. */
  21975. videojs$1.getPlayer = function (id) {
  21976. var players = Player.players;
  21977. var tag;
  21978. if (typeof id === 'string') {
  21979. var nId = normalizeId(id);
  21980. var player = players[nId];
  21981. if (player) {
  21982. return player;
  21983. }
  21984. tag = $('#' + nId);
  21985. } else {
  21986. tag = id;
  21987. }
  21988. if (isEl(tag)) {
  21989. var _tag = tag,
  21990. _player = _tag.player,
  21991. playerId = _tag.playerId; // Element may have a `player` property referring to an already created
  21992. // player instance. If so, return that.
  21993. if (_player || players[playerId]) {
  21994. return _player || players[playerId];
  21995. }
  21996. }
  21997. };
  21998. /**
  21999. * Returns an array of all current players.
  22000. *
  22001. * @return {Array}
  22002. * An array of all players. The array will be in the order that
  22003. * `Object.keys` provides, which could potentially vary between
  22004. * JavaScript engines.
  22005. *
  22006. */
  22007. videojs$1.getAllPlayers = function () {
  22008. return (// Disposed players leave a key with a `null` value, so we need to make sure
  22009. // we filter those out.
  22010. Object.keys(Player.players).map(function (k) {
  22011. return Player.players[k];
  22012. }).filter(Boolean)
  22013. );
  22014. };
  22015. videojs$1.players = Player.players;
  22016. videojs$1.getComponent = Component.getComponent;
  22017. /**
  22018. * Register a component so it can referred to by name. Used when adding to other
  22019. * components, either through addChild `component.addChild('myComponent')` or through
  22020. * default children options `{ children: ['myComponent'] }`.
  22021. *
  22022. * > NOTE: You could also just initialize the component before adding.
  22023. * `component.addChild(new MyComponent());`
  22024. *
  22025. * @param {string} name
  22026. * The class name of the component
  22027. *
  22028. * @param {Component} comp
  22029. * The component class
  22030. *
  22031. * @return {Component}
  22032. * The newly registered component
  22033. */
  22034. videojs$1.registerComponent = function (name$$1, comp) {
  22035. if (Tech.isTech(comp)) {
  22036. log.warn("The " + name$$1 + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
  22037. }
  22038. Component.registerComponent.call(Component, name$$1, comp);
  22039. };
  22040. videojs$1.getTech = Tech.getTech;
  22041. videojs$1.registerTech = Tech.registerTech;
  22042. videojs$1.use = use;
  22043. /**
  22044. * An object that can be returned by a middleware to signify
  22045. * that the middleware is being terminated.
  22046. *
  22047. * @type {object}
  22048. * @property {object} middleware.TERMINATOR
  22049. */
  22050. Object.defineProperty(videojs$1, 'middleware', {
  22051. value: {},
  22052. writeable: false,
  22053. enumerable: true
  22054. });
  22055. Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
  22056. value: TERMINATOR,
  22057. writeable: false,
  22058. enumerable: true
  22059. });
  22060. /**
  22061. * A reference to the {@link module:browser|browser utility module} as an object.
  22062. *
  22063. * @type {Object}
  22064. * @see {@link module:browser|browser}
  22065. */
  22066. videojs$1.browser = browser;
  22067. /**
  22068. * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
  22069. * included for backward-compatibility with 4.x.
  22070. *
  22071. * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
  22072. * @type {boolean}
  22073. */
  22074. videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
  22075. videojs$1.extend = extend;
  22076. videojs$1.mergeOptions = mergeOptions;
  22077. videojs$1.bind = bind;
  22078. videojs$1.registerPlugin = Plugin.registerPlugin;
  22079. videojs$1.deregisterPlugin = Plugin.deregisterPlugin;
  22080. /**
  22081. * Deprecated method to register a plugin with Video.js
  22082. *
  22083. * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
  22084. *
  22085. * @param {string} name
  22086. * The plugin name
  22087. *
  22088. * @param {Plugin|Function} plugin
  22089. * The plugin sub-class or function
  22090. */
  22091. videojs$1.plugin = function (name$$1, plugin) {
  22092. log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
  22093. return Plugin.registerPlugin(name$$1, plugin);
  22094. };
  22095. videojs$1.getPlugins = Plugin.getPlugins;
  22096. videojs$1.getPlugin = Plugin.getPlugin;
  22097. videojs$1.getPluginVersion = Plugin.getPluginVersion;
  22098. /**
  22099. * Adding languages so that they're available to all players.
  22100. * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
  22101. *
  22102. * @param {string} code
  22103. * The language code or dictionary property
  22104. *
  22105. * @param {Object} data
  22106. * The data values to be translated
  22107. *
  22108. * @return {Object}
  22109. * The resulting language dictionary object
  22110. */
  22111. videojs$1.addLanguage = function (code, data) {
  22112. var _mergeOptions;
  22113. code = ('' + code).toLowerCase();
  22114. videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
  22115. return videojs$1.options.languages[code];
  22116. };
  22117. /**
  22118. * A reference to the {@link module:log|log utility module} as an object.
  22119. *
  22120. * @type {Function}
  22121. * @see {@link module:log|log}
  22122. */
  22123. videojs$1.log = log;
  22124. videojs$1.createLogger = createLogger$1;
  22125. videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
  22126. videojs$1.formatTime = formatTime;
  22127. videojs$1.setFormatTime = setFormatTime;
  22128. videojs$1.resetFormatTime = resetFormatTime;
  22129. videojs$1.parseUrl = parseUrl;
  22130. videojs$1.isCrossOrigin = isCrossOrigin;
  22131. videojs$1.EventTarget = EventTarget;
  22132. videojs$1.on = on;
  22133. videojs$1.one = one;
  22134. videojs$1.off = off;
  22135. videojs$1.trigger = trigger;
  22136. /**
  22137. * A cross-browser XMLHttpRequest wrapper.
  22138. *
  22139. * @function
  22140. * @param {Object} options
  22141. * Settings for the request.
  22142. *
  22143. * @return {XMLHttpRequest|XDomainRequest}
  22144. * The request object.
  22145. *
  22146. * @see https://github.com/Raynos/xhr
  22147. */
  22148. videojs$1.xhr = xhr;
  22149. videojs$1.TextTrack = TextTrack;
  22150. videojs$1.AudioTrack = AudioTrack;
  22151. videojs$1.VideoTrack = VideoTrack;
  22152. ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
  22153. videojs$1[k] = function () {
  22154. log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
  22155. return Dom[k].apply(null, arguments);
  22156. };
  22157. });
  22158. videojs$1.computedStyle = computedStyle;
  22159. /**
  22160. * A reference to the {@link module:dom|DOM utility module} as an object.
  22161. *
  22162. * @type {Object}
  22163. * @see {@link module:dom|dom}
  22164. */
  22165. videojs$1.dom = Dom;
  22166. /**
  22167. * A reference to the {@link module:url|URL utility module} as an object.
  22168. *
  22169. * @type {Object}
  22170. * @see {@link module:url|url}
  22171. */
  22172. videojs$1.url = Url;
  22173. /**
  22174. * @videojs/http-streaming
  22175. * @version 1.10.1
  22176. * @copyright 2019 Brightcove, Inc
  22177. * @license Apache-2.0
  22178. */
  22179. /**
  22180. * @file resolve-url.js - Handling how URLs are resolved and manipulated
  22181. */
  22182. var resolveUrl = function resolveUrl(baseURL, relativeURL) {
  22183. // return early if we don't need to resolve
  22184. if (/^[a-z]+:/i.test(relativeURL)) {
  22185. return relativeURL;
  22186. } // if the base URL is relative then combine with the current location
  22187. if (!/\/\//i.test(baseURL)) {
  22188. baseURL = URLToolkit.buildAbsoluteURL(window$1.location.href, baseURL);
  22189. }
  22190. return URLToolkit.buildAbsoluteURL(baseURL, relativeURL);
  22191. };
  22192. /**
  22193. * Checks whether xhr request was redirected and returns correct url depending
  22194. * on `handleManifestRedirects` option
  22195. *
  22196. * @api private
  22197. *
  22198. * @param {String} url - an url being requested
  22199. * @param {XMLHttpRequest} req - xhr request result
  22200. *
  22201. * @return {String}
  22202. */
  22203. var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
  22204. // To understand how the responseURL below is set and generated:
  22205. // - https://fetch.spec.whatwg.org/#concept-response-url
  22206. // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
  22207. if (handleManifestRedirect && req.responseURL && url !== req.responseURL) {
  22208. return req.responseURL;
  22209. }
  22210. return url;
  22211. };
  22212. var classCallCheck = function classCallCheck(instance, Constructor) {
  22213. if (!(instance instanceof Constructor)) {
  22214. throw new TypeError("Cannot call a class as a function");
  22215. }
  22216. };
  22217. var createClass = function () {
  22218. function defineProperties(target, props) {
  22219. for (var i = 0; i < props.length; i++) {
  22220. var descriptor = props[i];
  22221. descriptor.enumerable = descriptor.enumerable || false;
  22222. descriptor.configurable = true;
  22223. if ("value" in descriptor) descriptor.writable = true;
  22224. Object.defineProperty(target, descriptor.key, descriptor);
  22225. }
  22226. }
  22227. return function (Constructor, protoProps, staticProps) {
  22228. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  22229. if (staticProps) defineProperties(Constructor, staticProps);
  22230. return Constructor;
  22231. };
  22232. }();
  22233. var get$1 = function get(object, property, receiver) {
  22234. if (object === null) object = Function.prototype;
  22235. var desc = Object.getOwnPropertyDescriptor(object, property);
  22236. if (desc === undefined) {
  22237. var parent = Object.getPrototypeOf(object);
  22238. if (parent === null) {
  22239. return undefined;
  22240. } else {
  22241. return get(parent, property, receiver);
  22242. }
  22243. } else if ("value" in desc) {
  22244. return desc.value;
  22245. } else {
  22246. var getter = desc.get;
  22247. if (getter === undefined) {
  22248. return undefined;
  22249. }
  22250. return getter.call(receiver);
  22251. }
  22252. };
  22253. var inherits = function inherits(subClass, superClass) {
  22254. if (typeof superClass !== "function" && superClass !== null) {
  22255. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  22256. }
  22257. subClass.prototype = Object.create(superClass && superClass.prototype, {
  22258. constructor: {
  22259. value: subClass,
  22260. enumerable: false,
  22261. writable: true,
  22262. configurable: true
  22263. }
  22264. });
  22265. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  22266. };
  22267. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  22268. if (!self) {
  22269. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  22270. }
  22271. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  22272. };
  22273. var slicedToArray = function () {
  22274. function sliceIterator(arr, i) {
  22275. var _arr = [];
  22276. var _n = true;
  22277. var _d = false;
  22278. var _e = undefined;
  22279. try {
  22280. for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
  22281. _arr.push(_s.value);
  22282. if (i && _arr.length === i) break;
  22283. }
  22284. } catch (err) {
  22285. _d = true;
  22286. _e = err;
  22287. } finally {
  22288. try {
  22289. if (!_n && _i["return"]) _i["return"]();
  22290. } finally {
  22291. if (_d) throw _e;
  22292. }
  22293. }
  22294. return _arr;
  22295. }
  22296. return function (arr, i) {
  22297. if (Array.isArray(arr)) {
  22298. return arr;
  22299. } else if (Symbol.iterator in Object(arr)) {
  22300. return sliceIterator(arr, i);
  22301. } else {
  22302. throw new TypeError("Invalid attempt to destructure non-iterable instance");
  22303. }
  22304. };
  22305. }();
  22306. /**
  22307. * @file playlist-loader.js
  22308. *
  22309. * A state machine that manages the loading, caching, and updating of
  22310. * M3U8 playlists.
  22311. *
  22312. */
  22313. var mergeOptions$1 = videojs$1.mergeOptions,
  22314. EventTarget$1 = videojs$1.EventTarget,
  22315. log$1 = videojs$1.log;
  22316. /**
  22317. * Loops through all supported media groups in master and calls the provided
  22318. * callback for each group
  22319. *
  22320. * @param {Object} master
  22321. * The parsed master manifest object
  22322. * @param {Function} callback
  22323. * Callback to call for each media group
  22324. */
  22325. var forEachMediaGroup = function forEachMediaGroup(master, callback) {
  22326. ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
  22327. for (var groupKey in master.mediaGroups[mediaType]) {
  22328. for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
  22329. var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
  22330. callback(mediaProperties, mediaType, groupKey, labelKey);
  22331. }
  22332. }
  22333. });
  22334. };
  22335. /**
  22336. * Returns a new array of segments that is the result of merging
  22337. * properties from an older list of segments onto an updated
  22338. * list. No properties on the updated playlist will be overridden.
  22339. *
  22340. * @param {Array} original the outdated list of segments
  22341. * @param {Array} update the updated list of segments
  22342. * @param {Number=} offset the index of the first update
  22343. * segment in the original segment list. For non-live playlists,
  22344. * this should always be zero and does not need to be
  22345. * specified. For live playlists, it should be the difference
  22346. * between the media sequence numbers in the original and updated
  22347. * playlists.
  22348. * @return a list of merged segment objects
  22349. */
  22350. var updateSegments = function updateSegments(original, update, offset) {
  22351. var result = update.slice();
  22352. offset = offset || 0;
  22353. var length = Math.min(original.length, update.length + offset);
  22354. for (var i = offset; i < length; i++) {
  22355. result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
  22356. }
  22357. return result;
  22358. };
  22359. var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
  22360. if (!segment.resolvedUri) {
  22361. segment.resolvedUri = resolveUrl(baseUri, segment.uri);
  22362. }
  22363. if (segment.key && !segment.key.resolvedUri) {
  22364. segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
  22365. }
  22366. if (segment.map && !segment.map.resolvedUri) {
  22367. segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
  22368. }
  22369. };
  22370. /**
  22371. * Returns a new master playlist that is the result of merging an
  22372. * updated media playlist into the original version. If the
  22373. * updated media playlist does not match any of the playlist
  22374. * entries in the original master playlist, null is returned.
  22375. *
  22376. * @param {Object} master a parsed master M3U8 object
  22377. * @param {Object} media a parsed media M3U8 object
  22378. * @return {Object} a new object that represents the original
  22379. * master playlist with the updated media playlist merged in, or
  22380. * null if the merge produced no change.
  22381. */
  22382. var updateMaster = function updateMaster(master, media) {
  22383. var result = mergeOptions$1(master, {});
  22384. var playlist = result.playlists[media.uri];
  22385. if (!playlist) {
  22386. return null;
  22387. } // consider the playlist unchanged if the number of segments is equal, the media
  22388. // sequence number is unchanged, and this playlist hasn't become the end of the playlist
  22389. if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) {
  22390. return null;
  22391. }
  22392. var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists
  22393. if (playlist.segments) {
  22394. mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
  22395. } // resolve any segment URIs to prevent us from having to do it later
  22396. mergedPlaylist.segments.forEach(function (segment) {
  22397. resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
  22398. }); // TODO Right now in the playlists array there are two references to each playlist, one
  22399. // that is referenced by index, and one by URI. The index reference may no longer be
  22400. // necessary.
  22401. for (var i = 0; i < result.playlists.length; i++) {
  22402. if (result.playlists[i].uri === media.uri) {
  22403. result.playlists[i] = mergedPlaylist;
  22404. }
  22405. }
  22406. result.playlists[media.uri] = mergedPlaylist;
  22407. return result;
  22408. };
  22409. var setupMediaPlaylists = function setupMediaPlaylists(master) {
  22410. // setup by-URI lookups and resolve media playlist URIs
  22411. var i = master.playlists.length;
  22412. while (i--) {
  22413. var playlist = master.playlists[i];
  22414. master.playlists[playlist.uri] = playlist;
  22415. playlist.resolvedUri = resolveUrl(master.uri, playlist.uri);
  22416. playlist.id = i;
  22417. if (!playlist.attributes) {
  22418. // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
  22419. // BANDWIDTH attribute, we can play the stream without it. This means a poorly
  22420. // formatted master playlist may not have an attribute list. An attributes
  22421. // property is added here to prevent undefined references when we encounter
  22422. // this scenario.
  22423. playlist.attributes = {};
  22424. log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
  22425. }
  22426. }
  22427. };
  22428. var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
  22429. forEachMediaGroup(master, function (properties) {
  22430. if (properties.uri) {
  22431. properties.resolvedUri = resolveUrl(master.uri, properties.uri);
  22432. }
  22433. });
  22434. };
  22435. /**
  22436. * Calculates the time to wait before refreshing a live playlist
  22437. *
  22438. * @param {Object} media
  22439. * The current media
  22440. * @param {Boolean} update
  22441. * True if there were any updates from the last refresh, false otherwise
  22442. * @return {Number}
  22443. * The time in ms to wait before refreshing the live playlist
  22444. */
  22445. var refreshDelay = function refreshDelay(media, update) {
  22446. var lastSegment = media.segments[media.segments.length - 1];
  22447. var delay = void 0;
  22448. if (update && lastSegment && lastSegment.duration) {
  22449. delay = lastSegment.duration * 1000;
  22450. } else {
  22451. // if the playlist is unchanged since the last reload or last segment duration
  22452. // cannot be determined, try again after half the target duration
  22453. delay = (media.targetDuration || 10) * 500;
  22454. }
  22455. return delay;
  22456. };
  22457. /**
  22458. * Load a playlist from a remote location
  22459. *
  22460. * @class PlaylistLoader
  22461. * @extends Stream
  22462. * @param {String} srcUrl the url to start with
  22463. * @param {Boolean} withCredentials the withCredentials xhr option
  22464. * @constructor
  22465. */
  22466. var PlaylistLoader = function (_EventTarget) {
  22467. inherits(PlaylistLoader, _EventTarget);
  22468. function PlaylistLoader(srcUrl, hls) {
  22469. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  22470. classCallCheck(this, PlaylistLoader);
  22471. var _this = possibleConstructorReturn(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
  22472. var _options$withCredenti = options.withCredentials,
  22473. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  22474. _options$handleManife = options.handleManifestRedirects,
  22475. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  22476. _this.srcUrl = srcUrl;
  22477. _this.hls_ = hls;
  22478. _this.withCredentials = withCredentials;
  22479. _this.handleManifestRedirects = handleManifestRedirects;
  22480. var hlsOptions = hls.options_;
  22481. _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || [];
  22482. _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || [];
  22483. if (!_this.srcUrl) {
  22484. throw new Error('A non-empty playlist URL is required');
  22485. } // initialize the loader state
  22486. _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
  22487. _this.on('mediaupdatetimeout', function () {
  22488. if (_this.state !== 'HAVE_METADATA') {
  22489. // only refresh the media playlist if no other activity is going on
  22490. return;
  22491. }
  22492. _this.state = 'HAVE_CURRENT_METADATA';
  22493. _this.request = _this.hls_.xhr({
  22494. uri: resolveUrl(_this.master.uri, _this.media().uri),
  22495. withCredentials: _this.withCredentials
  22496. }, function (error, req) {
  22497. // disposed
  22498. if (!_this.request) {
  22499. return;
  22500. }
  22501. if (error) {
  22502. return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
  22503. }
  22504. _this.haveMetadata(_this.request, _this.media().uri);
  22505. });
  22506. });
  22507. return _this;
  22508. }
  22509. createClass(PlaylistLoader, [{
  22510. key: 'playlistRequestError',
  22511. value: function playlistRequestError(xhr$$1, url, startingState) {
  22512. // any in-flight request is now finished
  22513. this.request = null;
  22514. if (startingState) {
  22515. this.state = startingState;
  22516. }
  22517. this.error = {
  22518. playlist: this.master.playlists[url],
  22519. status: xhr$$1.status,
  22520. message: 'HLS playlist request error at URL: ' + url,
  22521. responseText: xhr$$1.responseText,
  22522. code: xhr$$1.status >= 500 ? 4 : 2
  22523. };
  22524. this.trigger('error');
  22525. } // update the playlist loader's state in response to a new or
  22526. // updated playlist.
  22527. }, {
  22528. key: 'haveMetadata',
  22529. value: function haveMetadata(xhr$$1, url) {
  22530. var _this2 = this; // any in-flight request is now finished
  22531. this.request = null;
  22532. this.state = 'HAVE_METADATA';
  22533. var parser = new m3u8Parser.Parser(); // adding custom tag parsers
  22534. this.customTagParsers.forEach(function (customParser) {
  22535. return parser.addParser(customParser);
  22536. }); // adding custom tag mappers
  22537. this.customTagMappers.forEach(function (mapper) {
  22538. return parser.addTagMapper(mapper);
  22539. });
  22540. parser.push(xhr$$1.responseText);
  22541. parser.end();
  22542. parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make
  22543. // sure that the property is attached to avoid undefined reference errors
  22544. parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master
  22545. var update = updateMaster(this.master, parser.manifest);
  22546. this.targetDuration = parser.manifest.targetDuration;
  22547. if (update) {
  22548. this.master = update;
  22549. this.media_ = this.master.playlists[parser.manifest.uri];
  22550. } else {
  22551. this.trigger('playlistunchanged');
  22552. } // refresh live playlists after a target duration passes
  22553. if (!this.media().endList) {
  22554. window$1.clearTimeout(this.mediaUpdateTimeout);
  22555. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  22556. _this2.trigger('mediaupdatetimeout');
  22557. }, refreshDelay(this.media(), !!update));
  22558. }
  22559. this.trigger('loadedplaylist');
  22560. }
  22561. /**
  22562. * Abort any outstanding work and clean up.
  22563. */
  22564. }, {
  22565. key: 'dispose',
  22566. value: function dispose() {
  22567. this.stopRequest();
  22568. window$1.clearTimeout(this.mediaUpdateTimeout);
  22569. }
  22570. }, {
  22571. key: 'stopRequest',
  22572. value: function stopRequest() {
  22573. if (this.request) {
  22574. var oldRequest = this.request;
  22575. this.request = null;
  22576. oldRequest.onreadystatechange = null;
  22577. oldRequest.abort();
  22578. }
  22579. }
  22580. /**
  22581. * When called without any arguments, returns the currently
  22582. * active media playlist. When called with a single argument,
  22583. * triggers the playlist loader to asynchronously switch to the
  22584. * specified media playlist. Calling this method while the
  22585. * loader is in the HAVE_NOTHING causes an error to be emitted
  22586. * but otherwise has no effect.
  22587. *
  22588. * @param {Object=} playlist the parsed media playlist
  22589. * object to switch to
  22590. * @return {Playlist} the current loaded media
  22591. */
  22592. }, {
  22593. key: 'media',
  22594. value: function media(playlist) {
  22595. var _this3 = this; // getter
  22596. if (!playlist) {
  22597. return this.media_;
  22598. } // setter
  22599. if (this.state === 'HAVE_NOTHING') {
  22600. throw new Error('Cannot switch media playlist from ' + this.state);
  22601. }
  22602. var startingState = this.state; // find the playlist object if the target playlist has been
  22603. // specified by URI
  22604. if (typeof playlist === 'string') {
  22605. if (!this.master.playlists[playlist]) {
  22606. throw new Error('Unknown playlist URI: ' + playlist);
  22607. }
  22608. playlist = this.master.playlists[playlist];
  22609. }
  22610. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately
  22611. if (this.master.playlists[playlist.uri].endList) {
  22612. // abort outstanding playlist requests
  22613. if (this.request) {
  22614. this.request.onreadystatechange = null;
  22615. this.request.abort();
  22616. this.request = null;
  22617. }
  22618. this.state = 'HAVE_METADATA';
  22619. this.media_ = playlist; // trigger media change if the active media has been updated
  22620. if (mediaChange) {
  22621. this.trigger('mediachanging');
  22622. this.trigger('mediachange');
  22623. }
  22624. return;
  22625. } // switching to the active playlist is a no-op
  22626. if (!mediaChange) {
  22627. return;
  22628. }
  22629. this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
  22630. if (this.request) {
  22631. if (playlist.resolvedUri === this.request.url) {
  22632. // requesting to switch to the same playlist multiple times
  22633. // has no effect after the first
  22634. return;
  22635. }
  22636. this.request.onreadystatechange = null;
  22637. this.request.abort();
  22638. this.request = null;
  22639. } // request the new playlist
  22640. if (this.media_) {
  22641. this.trigger('mediachanging');
  22642. }
  22643. this.request = this.hls_.xhr({
  22644. uri: playlist.resolvedUri,
  22645. withCredentials: this.withCredentials
  22646. }, function (error, req) {
  22647. // disposed
  22648. if (!_this3.request) {
  22649. return;
  22650. }
  22651. playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req);
  22652. if (error) {
  22653. return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
  22654. }
  22655. _this3.haveMetadata(req, playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  22656. if (startingState === 'HAVE_MASTER') {
  22657. _this3.trigger('loadedmetadata');
  22658. } else {
  22659. _this3.trigger('mediachange');
  22660. }
  22661. });
  22662. }
  22663. /**
  22664. * pause loading of the playlist
  22665. */
  22666. }, {
  22667. key: 'pause',
  22668. value: function pause() {
  22669. this.stopRequest();
  22670. window$1.clearTimeout(this.mediaUpdateTimeout);
  22671. if (this.state === 'HAVE_NOTHING') {
  22672. // If we pause the loader before any data has been retrieved, its as if we never
  22673. // started, so reset to an unstarted state.
  22674. this.started = false;
  22675. } // Need to restore state now that no activity is happening
  22676. if (this.state === 'SWITCHING_MEDIA') {
  22677. // if the loader was in the process of switching media, it should either return to
  22678. // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
  22679. // playlist yet. This is determined by the existence of loader.media_
  22680. if (this.media_) {
  22681. this.state = 'HAVE_METADATA';
  22682. } else {
  22683. this.state = 'HAVE_MASTER';
  22684. }
  22685. } else if (this.state === 'HAVE_CURRENT_METADATA') {
  22686. this.state = 'HAVE_METADATA';
  22687. }
  22688. }
  22689. /**
  22690. * start loading of the playlist
  22691. */
  22692. }, {
  22693. key: 'load',
  22694. value: function load(isFinalRendition) {
  22695. var _this4 = this;
  22696. window$1.clearTimeout(this.mediaUpdateTimeout);
  22697. var media = this.media();
  22698. if (isFinalRendition) {
  22699. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  22700. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  22701. return _this4.load();
  22702. }, delay);
  22703. return;
  22704. }
  22705. if (!this.started) {
  22706. this.start();
  22707. return;
  22708. }
  22709. if (media && !media.endList) {
  22710. this.trigger('mediaupdatetimeout');
  22711. } else {
  22712. this.trigger('loadedplaylist');
  22713. }
  22714. }
  22715. /**
  22716. * start loading of the playlist
  22717. */
  22718. }, {
  22719. key: 'start',
  22720. value: function start() {
  22721. var _this5 = this;
  22722. this.started = true; // request the specified URL
  22723. this.request = this.hls_.xhr({
  22724. uri: this.srcUrl,
  22725. withCredentials: this.withCredentials
  22726. }, function (error, req) {
  22727. // disposed
  22728. if (!_this5.request) {
  22729. return;
  22730. } // clear the loader's request reference
  22731. _this5.request = null;
  22732. if (error) {
  22733. _this5.error = {
  22734. status: req.status,
  22735. message: 'HLS playlist request error at URL: ' + _this5.srcUrl,
  22736. responseText: req.responseText,
  22737. // MEDIA_ERR_NETWORK
  22738. code: 2
  22739. };
  22740. if (_this5.state === 'HAVE_NOTHING') {
  22741. _this5.started = false;
  22742. }
  22743. return _this5.trigger('error');
  22744. }
  22745. var parser = new m3u8Parser.Parser(); // adding custom tag parsers
  22746. _this5.customTagParsers.forEach(function (customParser) {
  22747. return parser.addParser(customParser);
  22748. }); // adding custom tag mappers
  22749. _this5.customTagMappers.forEach(function (mapper) {
  22750. return parser.addTagMapper(mapper);
  22751. });
  22752. parser.push(req.responseText);
  22753. parser.end();
  22754. _this5.state = 'HAVE_MASTER';
  22755. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  22756. parser.manifest.uri = _this5.srcUrl; // loaded a master playlist
  22757. if (parser.manifest.playlists) {
  22758. _this5.master = parser.manifest;
  22759. setupMediaPlaylists(_this5.master);
  22760. resolveMediaGroupUris(_this5.master);
  22761. _this5.trigger('loadedplaylist');
  22762. if (!_this5.request) {
  22763. // no media playlist was specifically selected so start
  22764. // from the first listed one
  22765. _this5.media(parser.manifest.playlists[0]);
  22766. }
  22767. return;
  22768. } // loaded a media playlist
  22769. // infer a master playlist if none was previously requested
  22770. _this5.master = {
  22771. mediaGroups: {
  22772. 'AUDIO': {},
  22773. 'VIDEO': {},
  22774. 'CLOSED-CAPTIONS': {},
  22775. 'SUBTITLES': {}
  22776. },
  22777. uri: window$1.location.href,
  22778. playlists: [{
  22779. uri: _this5.srcUrl,
  22780. id: 0,
  22781. resolvedUri: _this5.srcUrl,
  22782. // m3u8-parser does not attach an attributes property to media playlists so make
  22783. // sure that the property is attached to avoid undefined reference errors
  22784. attributes: {}
  22785. }]
  22786. };
  22787. _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
  22788. _this5.haveMetadata(req, _this5.srcUrl);
  22789. return _this5.trigger('loadedmetadata');
  22790. });
  22791. }
  22792. }]);
  22793. return PlaylistLoader;
  22794. }(EventTarget$1);
  22795. /**
  22796. * @file playlist.js
  22797. *
  22798. * Playlist related utilities.
  22799. */
  22800. var createTimeRange = videojs$1.createTimeRange;
  22801. /**
  22802. * walk backward until we find a duration we can use
  22803. * or return a failure
  22804. *
  22805. * @param {Playlist} playlist the playlist to walk through
  22806. * @param {Number} endSequence the mediaSequence to stop walking on
  22807. */
  22808. var backwardDuration = function backwardDuration(playlist, endSequence) {
  22809. var result = 0;
  22810. var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
  22811. // the interval, use it
  22812. var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
  22813. // information that is earlier than endSequence
  22814. if (segment) {
  22815. if (typeof segment.start !== 'undefined') {
  22816. return {
  22817. result: segment.start,
  22818. precise: true
  22819. };
  22820. }
  22821. if (typeof segment.end !== 'undefined') {
  22822. return {
  22823. result: segment.end - segment.duration,
  22824. precise: true
  22825. };
  22826. }
  22827. }
  22828. while (i--) {
  22829. segment = playlist.segments[i];
  22830. if (typeof segment.end !== 'undefined') {
  22831. return {
  22832. result: result + segment.end,
  22833. precise: true
  22834. };
  22835. }
  22836. result += segment.duration;
  22837. if (typeof segment.start !== 'undefined') {
  22838. return {
  22839. result: result + segment.start,
  22840. precise: true
  22841. };
  22842. }
  22843. }
  22844. return {
  22845. result: result,
  22846. precise: false
  22847. };
  22848. };
  22849. /**
  22850. * walk forward until we find a duration we can use
  22851. * or return a failure
  22852. *
  22853. * @param {Playlist} playlist the playlist to walk through
  22854. * @param {Number} endSequence the mediaSequence to stop walking on
  22855. */
  22856. var forwardDuration = function forwardDuration(playlist, endSequence) {
  22857. var result = 0;
  22858. var segment = void 0;
  22859. var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
  22860. // information
  22861. for (; i < playlist.segments.length; i++) {
  22862. segment = playlist.segments[i];
  22863. if (typeof segment.start !== 'undefined') {
  22864. return {
  22865. result: segment.start - result,
  22866. precise: true
  22867. };
  22868. }
  22869. result += segment.duration;
  22870. if (typeof segment.end !== 'undefined') {
  22871. return {
  22872. result: segment.end - result,
  22873. precise: true
  22874. };
  22875. }
  22876. } // indicate we didn't find a useful duration estimate
  22877. return {
  22878. result: -1,
  22879. precise: false
  22880. };
  22881. };
  22882. /**
  22883. * Calculate the media duration from the segments associated with a
  22884. * playlist. The duration of a subinterval of the available segments
  22885. * may be calculated by specifying an end index.
  22886. *
  22887. * @param {Object} playlist a media playlist object
  22888. * @param {Number=} endSequence an exclusive upper boundary
  22889. * for the playlist. Defaults to playlist length.
  22890. * @param {Number} expired the amount of time that has dropped
  22891. * off the front of the playlist in a live scenario
  22892. * @return {Number} the duration between the first available segment
  22893. * and end index.
  22894. */
  22895. var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
  22896. var backward = void 0;
  22897. var forward = void 0;
  22898. if (typeof endSequence === 'undefined') {
  22899. endSequence = playlist.mediaSequence + playlist.segments.length;
  22900. }
  22901. if (endSequence < playlist.mediaSequence) {
  22902. return 0;
  22903. } // do a backward walk to estimate the duration
  22904. backward = backwardDuration(playlist, endSequence);
  22905. if (backward.precise) {
  22906. // if we were able to base our duration estimate on timing
  22907. // information provided directly from the Media Source, return
  22908. // it
  22909. return backward.result;
  22910. } // walk forward to see if a precise duration estimate can be made
  22911. // that way
  22912. forward = forwardDuration(playlist, endSequence);
  22913. if (forward.precise) {
  22914. // we found a segment that has been buffered and so it's
  22915. // position is known precisely
  22916. return forward.result;
  22917. } // return the less-precise, playlist-based duration estimate
  22918. return backward.result + expired;
  22919. };
  22920. /**
  22921. * Calculates the duration of a playlist. If a start and end index
  22922. * are specified, the duration will be for the subset of the media
  22923. * timeline between those two indices. The total duration for live
  22924. * playlists is always Infinity.
  22925. *
  22926. * @param {Object} playlist a media playlist object
  22927. * @param {Number=} endSequence an exclusive upper
  22928. * boundary for the playlist. Defaults to the playlist media
  22929. * sequence number plus its length.
  22930. * @param {Number=} expired the amount of time that has
  22931. * dropped off the front of the playlist in a live scenario
  22932. * @return {Number} the duration between the start index and end
  22933. * index.
  22934. */
  22935. var duration = function duration(playlist, endSequence, expired) {
  22936. if (!playlist) {
  22937. return 0;
  22938. }
  22939. if (typeof expired !== 'number') {
  22940. expired = 0;
  22941. } // if a slice of the total duration is not requested, use
  22942. // playlist-level duration indicators when they're present
  22943. if (typeof endSequence === 'undefined') {
  22944. // if present, use the duration specified in the playlist
  22945. if (playlist.totalDuration) {
  22946. return playlist.totalDuration;
  22947. } // duration should be Infinity for live playlists
  22948. if (!playlist.endList) {
  22949. return window$1.Infinity;
  22950. }
  22951. } // calculate the total duration based on the segment durations
  22952. return intervalDuration(playlist, endSequence, expired);
  22953. };
  22954. /**
  22955. * Calculate the time between two indexes in the current playlist
  22956. * neight the start- nor the end-index need to be within the current
  22957. * playlist in which case, the targetDuration of the playlist is used
  22958. * to approximate the durations of the segments
  22959. *
  22960. * @param {Object} playlist a media playlist object
  22961. * @param {Number} startIndex
  22962. * @param {Number} endIndex
  22963. * @return {Number} the number of seconds between startIndex and endIndex
  22964. */
  22965. var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
  22966. var durations = 0;
  22967. if (startIndex > endIndex) {
  22968. var _ref = [endIndex, startIndex];
  22969. startIndex = _ref[0];
  22970. endIndex = _ref[1];
  22971. }
  22972. if (startIndex < 0) {
  22973. for (var i = startIndex; i < Math.min(0, endIndex); i++) {
  22974. durations += playlist.targetDuration;
  22975. }
  22976. startIndex = 0;
  22977. }
  22978. for (var _i = startIndex; _i < endIndex; _i++) {
  22979. durations += playlist.segments[_i].duration;
  22980. }
  22981. return durations;
  22982. };
  22983. /**
  22984. * Determines the media index of the segment corresponding to the safe edge of the live
  22985. * window which is the duration of the last segment plus 2 target durations from the end
  22986. * of the playlist.
  22987. *
  22988. * @param {Object} playlist
  22989. * a media playlist object
  22990. * @return {Number}
  22991. * The media index of the segment at the safe live point. 0 if there is no "safe"
  22992. * point.
  22993. * @function safeLiveIndex
  22994. */
  22995. var safeLiveIndex = function safeLiveIndex(playlist) {
  22996. if (!playlist.segments.length) {
  22997. return 0;
  22998. }
  22999. var i = playlist.segments.length - 1;
  23000. var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
  23001. var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
  23002. while (i--) {
  23003. distanceFromEnd += playlist.segments[i].duration;
  23004. if (distanceFromEnd >= safeDistance) {
  23005. break;
  23006. }
  23007. }
  23008. return Math.max(0, i);
  23009. };
  23010. /**
  23011. * Calculates the playlist end time
  23012. *
  23013. * @param {Object} playlist a media playlist object
  23014. * @param {Number=} expired the amount of time that has
  23015. * dropped off the front of the playlist in a live scenario
  23016. * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
  23017. * playlist end calculation should consider the safe live end
  23018. * (truncate the playlist end by three segments). This is normally
  23019. * used for calculating the end of the playlist's seekable range.
  23020. * @returns {Number} the end time of playlist
  23021. * @function playlistEnd
  23022. */
  23023. var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
  23024. if (!playlist || !playlist.segments) {
  23025. return null;
  23026. }
  23027. if (playlist.endList) {
  23028. return duration(playlist);
  23029. }
  23030. if (expired === null) {
  23031. return null;
  23032. }
  23033. expired = expired || 0;
  23034. var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
  23035. return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
  23036. };
  23037. /**
  23038. * Calculates the interval of time that is currently seekable in a
  23039. * playlist. The returned time ranges are relative to the earliest
  23040. * moment in the specified playlist that is still available. A full
  23041. * seekable implementation for live streams would need to offset
  23042. * these values by the duration of content that has expired from the
  23043. * stream.
  23044. *
  23045. * @param {Object} playlist a media playlist object
  23046. * dropped off the front of the playlist in a live scenario
  23047. * @param {Number=} expired the amount of time that has
  23048. * dropped off the front of the playlist in a live scenario
  23049. * @return {TimeRanges} the periods of time that are valid targets
  23050. * for seeking
  23051. */
  23052. var seekable = function seekable(playlist, expired) {
  23053. var useSafeLiveEnd = true;
  23054. var seekableStart = expired || 0;
  23055. var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
  23056. if (seekableEnd === null) {
  23057. return createTimeRange();
  23058. }
  23059. return createTimeRange(seekableStart, seekableEnd);
  23060. };
  23061. var isWholeNumber = function isWholeNumber(num) {
  23062. return num - Math.floor(num) === 0;
  23063. };
  23064. var roundSignificantDigit = function roundSignificantDigit(increment, num) {
  23065. // If we have a whole number, just add 1 to it
  23066. if (isWholeNumber(num)) {
  23067. return num + increment * 0.1;
  23068. }
  23069. var numDecimalDigits = num.toString().split('.')[1].length;
  23070. for (var i = 1; i <= numDecimalDigits; i++) {
  23071. var scale = Math.pow(10, i);
  23072. var temp = num * scale;
  23073. if (isWholeNumber(temp) || i === numDecimalDigits) {
  23074. return (temp + increment) / scale;
  23075. }
  23076. }
  23077. };
  23078. var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
  23079. var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
  23080. /**
  23081. * Determine the index and estimated starting time of the segment that
  23082. * contains a specified playback position in a media playlist.
  23083. *
  23084. * @param {Object} playlist the media playlist to query
  23085. * @param {Number} currentTime The number of seconds since the earliest
  23086. * possible position to determine the containing segment for
  23087. * @param {Number} startIndex
  23088. * @param {Number} startTime
  23089. * @return {Object}
  23090. */
  23091. var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
  23092. var i = void 0;
  23093. var segment = void 0;
  23094. var numSegments = playlist.segments.length;
  23095. var time = currentTime - startTime;
  23096. if (time < 0) {
  23097. // Walk backward from startIndex in the playlist, adding durations
  23098. // until we find a segment that contains `time` and return it
  23099. if (startIndex > 0) {
  23100. for (i = startIndex - 1; i >= 0; i--) {
  23101. segment = playlist.segments[i];
  23102. time += floorLeastSignificantDigit(segment.duration);
  23103. if (time > 0) {
  23104. return {
  23105. mediaIndex: i,
  23106. startTime: startTime - sumDurations(playlist, startIndex, i)
  23107. };
  23108. }
  23109. }
  23110. } // We were unable to find a good segment within the playlist
  23111. // so select the first segment
  23112. return {
  23113. mediaIndex: 0,
  23114. startTime: currentTime
  23115. };
  23116. } // When startIndex is negative, we first walk forward to first segment
  23117. // adding target durations. If we "run out of time" before getting to
  23118. // the first segment, return the first segment
  23119. if (startIndex < 0) {
  23120. for (i = startIndex; i < 0; i++) {
  23121. time -= playlist.targetDuration;
  23122. if (time < 0) {
  23123. return {
  23124. mediaIndex: 0,
  23125. startTime: currentTime
  23126. };
  23127. }
  23128. }
  23129. startIndex = 0;
  23130. } // Walk forward from startIndex in the playlist, subtracting durations
  23131. // until we find a segment that contains `time` and return it
  23132. for (i = startIndex; i < numSegments; i++) {
  23133. segment = playlist.segments[i];
  23134. time -= ceilLeastSignificantDigit(segment.duration);
  23135. if (time < 0) {
  23136. return {
  23137. mediaIndex: i,
  23138. startTime: startTime + sumDurations(playlist, startIndex, i)
  23139. };
  23140. }
  23141. } // We are out of possible candidates so load the last one...
  23142. return {
  23143. mediaIndex: numSegments - 1,
  23144. startTime: currentTime
  23145. };
  23146. };
  23147. /**
  23148. * Check whether the playlist is blacklisted or not.
  23149. *
  23150. * @param {Object} playlist the media playlist object
  23151. * @return {boolean} whether the playlist is blacklisted or not
  23152. * @function isBlacklisted
  23153. */
  23154. var isBlacklisted = function isBlacklisted(playlist) {
  23155. return playlist.excludeUntil && playlist.excludeUntil > Date.now();
  23156. };
  23157. /**
  23158. * Check whether the playlist is compatible with current playback configuration or has
  23159. * been blacklisted permanently for being incompatible.
  23160. *
  23161. * @param {Object} playlist the media playlist object
  23162. * @return {boolean} whether the playlist is incompatible or not
  23163. * @function isIncompatible
  23164. */
  23165. var isIncompatible = function isIncompatible(playlist) {
  23166. return playlist.excludeUntil && playlist.excludeUntil === Infinity;
  23167. };
  23168. /**
  23169. * Check whether the playlist is enabled or not.
  23170. *
  23171. * @param {Object} playlist the media playlist object
  23172. * @return {boolean} whether the playlist is enabled or not
  23173. * @function isEnabled
  23174. */
  23175. var isEnabled = function isEnabled(playlist) {
  23176. var blacklisted = isBlacklisted(playlist);
  23177. return !playlist.disabled && !blacklisted;
  23178. };
  23179. /**
  23180. * Check whether the playlist has been manually disabled through the representations api.
  23181. *
  23182. * @param {Object} playlist the media playlist object
  23183. * @return {boolean} whether the playlist is disabled manually or not
  23184. * @function isDisabled
  23185. */
  23186. var isDisabled = function isDisabled(playlist) {
  23187. return playlist.disabled;
  23188. };
  23189. /**
  23190. * Returns whether the current playlist is an AES encrypted HLS stream
  23191. *
  23192. * @return {Boolean} true if it's an AES encrypted HLS stream
  23193. */
  23194. var isAes = function isAes(media) {
  23195. for (var i = 0; i < media.segments.length; i++) {
  23196. if (media.segments[i].key) {
  23197. return true;
  23198. }
  23199. }
  23200. return false;
  23201. };
  23202. /**
  23203. * Returns whether the current playlist contains fMP4
  23204. *
  23205. * @return {Boolean} true if the playlist contains fMP4
  23206. */
  23207. var isFmp4 = function isFmp4(media) {
  23208. for (var i = 0; i < media.segments.length; i++) {
  23209. if (media.segments[i].map) {
  23210. return true;
  23211. }
  23212. }
  23213. return false;
  23214. };
  23215. /**
  23216. * Checks if the playlist has a value for the specified attribute
  23217. *
  23218. * @param {String} attr
  23219. * Attribute to check for
  23220. * @param {Object} playlist
  23221. * The media playlist object
  23222. * @return {Boolean}
  23223. * Whether the playlist contains a value for the attribute or not
  23224. * @function hasAttribute
  23225. */
  23226. var hasAttribute = function hasAttribute(attr, playlist) {
  23227. return playlist.attributes && playlist.attributes[attr];
  23228. };
  23229. /**
  23230. * Estimates the time required to complete a segment download from the specified playlist
  23231. *
  23232. * @param {Number} segmentDuration
  23233. * Duration of requested segment
  23234. * @param {Number} bandwidth
  23235. * Current measured bandwidth of the player
  23236. * @param {Object} playlist
  23237. * The media playlist object
  23238. * @param {Number=} bytesReceived
  23239. * Number of bytes already received for the request. Defaults to 0
  23240. * @return {Number|NaN}
  23241. * The estimated time to request the segment. NaN if bandwidth information for
  23242. * the given playlist is unavailable
  23243. * @function estimateSegmentRequestTime
  23244. */
  23245. var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
  23246. var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  23247. if (!hasAttribute('BANDWIDTH', playlist)) {
  23248. return NaN;
  23249. }
  23250. var size = segmentDuration * playlist.attributes.BANDWIDTH;
  23251. return (size - bytesReceived * 8) / bandwidth;
  23252. };
  23253. /*
  23254. * Returns whether the current playlist is the lowest rendition
  23255. *
  23256. * @return {Boolean} true if on lowest rendition
  23257. */
  23258. var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
  23259. if (master.playlists.length === 1) {
  23260. return true;
  23261. }
  23262. var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
  23263. return master.playlists.filter(function (playlist) {
  23264. if (!isEnabled(playlist)) {
  23265. return false;
  23266. }
  23267. return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
  23268. }).length === 0;
  23269. }; // exports
  23270. var Playlist = {
  23271. duration: duration,
  23272. seekable: seekable,
  23273. safeLiveIndex: safeLiveIndex,
  23274. getMediaInfoForTime: getMediaInfoForTime,
  23275. isEnabled: isEnabled,
  23276. isDisabled: isDisabled,
  23277. isBlacklisted: isBlacklisted,
  23278. isIncompatible: isIncompatible,
  23279. playlistEnd: playlistEnd,
  23280. isAes: isAes,
  23281. isFmp4: isFmp4,
  23282. hasAttribute: hasAttribute,
  23283. estimateSegmentRequestTime: estimateSegmentRequestTime,
  23284. isLowestEnabledRendition: isLowestEnabledRendition
  23285. };
  23286. /**
  23287. * @file xhr.js
  23288. */
  23289. var videojsXHR = videojs$1.xhr,
  23290. mergeOptions$1$1 = videojs$1.mergeOptions;
  23291. var xhrFactory = function xhrFactory() {
  23292. var xhr$$1 = function XhrFunction(options, callback) {
  23293. // Add a default timeout for all hls requests
  23294. options = mergeOptions$1$1({
  23295. timeout: 45e3
  23296. }, options); // Allow an optional user-specified function to modify the option
  23297. // object before we construct the xhr request
  23298. var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
  23299. if (beforeRequest && typeof beforeRequest === 'function') {
  23300. var newOptions = beforeRequest(options);
  23301. if (newOptions) {
  23302. options = newOptions;
  23303. }
  23304. }
  23305. var request = videojsXHR(options, function (error, response) {
  23306. var reqResponse = request.response;
  23307. if (!error && reqResponse) {
  23308. request.responseTime = Date.now();
  23309. request.roundTripTime = request.responseTime - request.requestTime;
  23310. request.bytesReceived = reqResponse.byteLength || reqResponse.length;
  23311. if (!request.bandwidth) {
  23312. request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
  23313. }
  23314. }
  23315. if (response.headers) {
  23316. request.responseHeaders = response.headers;
  23317. } // videojs.xhr now uses a specific code on the error
  23318. // object to signal that a request has timed out instead
  23319. // of setting a boolean on the request object
  23320. if (error && error.code === 'ETIMEDOUT') {
  23321. request.timedout = true;
  23322. } // videojs.xhr no longer considers status codes outside of 200 and 0
  23323. // (for file uris) to be errors, but the old XHR did, so emulate that
  23324. // behavior. Status 206 may be used in response to byterange requests.
  23325. if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
  23326. error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
  23327. }
  23328. callback(error, request);
  23329. });
  23330. var originalAbort = request.abort;
  23331. request.abort = function () {
  23332. request.aborted = true;
  23333. return originalAbort.apply(request, arguments);
  23334. };
  23335. request.uri = options.uri;
  23336. request.requestTime = Date.now();
  23337. return request;
  23338. };
  23339. return xhr$$1;
  23340. };
  23341. /**
  23342. * Turns segment byterange into a string suitable for use in
  23343. * HTTP Range requests
  23344. *
  23345. * @param {Object} byterange - an object with two values defining the start and end
  23346. * of a byte-range
  23347. */
  23348. var byterangeStr = function byterangeStr(byterange) {
  23349. var byterangeStart = void 0;
  23350. var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range
  23351. // header uses inclusive ranges
  23352. byterangeEnd = byterange.offset + byterange.length - 1;
  23353. byterangeStart = byterange.offset;
  23354. return 'bytes=' + byterangeStart + '-' + byterangeEnd;
  23355. };
  23356. /**
  23357. * Defines headers for use in the xhr request for a particular segment.
  23358. *
  23359. * @param {Object} segment - a simplified copy of the segmentInfo object
  23360. * from SegmentLoader
  23361. */
  23362. var segmentXhrHeaders = function segmentXhrHeaders(segment) {
  23363. var headers = {};
  23364. if (segment.byterange) {
  23365. headers.Range = byterangeStr(segment.byterange);
  23366. }
  23367. return headers;
  23368. };
  23369. /**
  23370. * @file bin-utils.js
  23371. */
  23372. /**
  23373. * convert a TimeRange to text
  23374. *
  23375. * @param {TimeRange} range the timerange to use for conversion
  23376. * @param {Number} i the iterator on the range to convert
  23377. */
  23378. var textRange = function textRange(range, i) {
  23379. return range.start(i) + '-' + range.end(i);
  23380. };
  23381. /**
  23382. * format a number as hex string
  23383. *
  23384. * @param {Number} e The number
  23385. * @param {Number} i the iterator
  23386. */
  23387. var formatHexString = function formatHexString(e, i) {
  23388. var value = e.toString(16);
  23389. return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
  23390. };
  23391. var formatAsciiString = function formatAsciiString(e) {
  23392. if (e >= 0x20 && e < 0x7e) {
  23393. return String.fromCharCode(e);
  23394. }
  23395. return '.';
  23396. };
  23397. /**
  23398. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  23399. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  23400. *
  23401. * @param {Object} message
  23402. * Object of properties and values to send to the web worker
  23403. * @return {Object}
  23404. * Modified message with TypedArray values expanded
  23405. * @function createTransferableMessage
  23406. */
  23407. var createTransferableMessage = function createTransferableMessage(message) {
  23408. var transferable = {};
  23409. Object.keys(message).forEach(function (key) {
  23410. var value = message[key];
  23411. if (ArrayBuffer.isView(value)) {
  23412. transferable[key] = {
  23413. bytes: value.buffer,
  23414. byteOffset: value.byteOffset,
  23415. byteLength: value.byteLength
  23416. };
  23417. } else {
  23418. transferable[key] = value;
  23419. }
  23420. });
  23421. return transferable;
  23422. };
  23423. /**
  23424. * Returns a unique string identifier for a media initialization
  23425. * segment.
  23426. */
  23427. var initSegmentId = function initSegmentId(initSegment) {
  23428. var byterange = initSegment.byterange || {
  23429. length: Infinity,
  23430. offset: 0
  23431. };
  23432. return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
  23433. };
  23434. /**
  23435. * Returns a unique string identifier for a media segment key.
  23436. */
  23437. var segmentKeyId = function segmentKeyId(key) {
  23438. return key.resolvedUri;
  23439. };
  23440. /**
  23441. * utils to help dump binary data to the console
  23442. */
  23443. var hexDump = function hexDump(data) {
  23444. var bytes = Array.prototype.slice.call(data);
  23445. var step = 16;
  23446. var result = '';
  23447. var hex = void 0;
  23448. var ascii = void 0;
  23449. for (var j = 0; j < bytes.length / step; j++) {
  23450. hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
  23451. ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
  23452. result += hex + ' ' + ascii + '\n';
  23453. }
  23454. return result;
  23455. };
  23456. var tagDump = function tagDump(_ref) {
  23457. var bytes = _ref.bytes;
  23458. return hexDump(bytes);
  23459. };
  23460. var textRanges = function textRanges(ranges) {
  23461. var result = '';
  23462. var i = void 0;
  23463. for (i = 0; i < ranges.length; i++) {
  23464. result += textRange(ranges, i) + ' ';
  23465. }
  23466. return result;
  23467. };
  23468. var utils =
  23469. /*#__PURE__*/
  23470. Object.freeze({
  23471. createTransferableMessage: createTransferableMessage,
  23472. initSegmentId: initSegmentId,
  23473. segmentKeyId: segmentKeyId,
  23474. hexDump: hexDump,
  23475. tagDump: tagDump,
  23476. textRanges: textRanges
  23477. }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
  23478. // Add 25% to the segment duration to account for small discrepencies in segment timing.
  23479. // 25% was arbitrarily chosen, and may need to be refined over time.
  23480. var SEGMENT_END_FUDGE_PERCENT = 0.25;
  23481. /**
  23482. * Converts a player time (any time that can be gotten/set from player.currentTime(),
  23483. * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
  23484. * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
  23485. *
  23486. * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
  23487. * point" (a point where we have a mapping from program time to player time, with player
  23488. * time being the post transmux start of the segment).
  23489. *
  23490. * For more details, see [this doc](../../docs/program-time-from-player-time.md).
  23491. *
  23492. * @param {Number} playerTime the player time
  23493. * @param {Object} segment the segment which contains the player time
  23494. * @return {Date} program time
  23495. */
  23496. var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
  23497. if (!segment.dateTimeObject) {
  23498. // Can't convert without an "anchor point" for the program time (i.e., a time that can
  23499. // be used to map the start of a segment with a real world time).
  23500. return null;
  23501. }
  23502. var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
  23503. var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
  23504. var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
  23505. var offsetFromSegmentStart = playerTime - startOfSegment;
  23506. return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
  23507. };
  23508. var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
  23509. return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
  23510. };
  23511. /**
  23512. * Finds a segment that contains the time requested given as an ISO-8601 string. The
  23513. * returned segment might be an estimate or an accurate match.
  23514. *
  23515. * @param {String} programTime The ISO-8601 programTime to find a match for
  23516. * @param {Object} playlist A playlist object to search within
  23517. */
  23518. var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
  23519. // Assumptions:
  23520. // - verifyProgramDateTimeTags has already been run
  23521. // - live streams have been started
  23522. var dateTimeObject = void 0;
  23523. try {
  23524. dateTimeObject = new Date(programTime);
  23525. } catch (e) {
  23526. return null;
  23527. }
  23528. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  23529. return null;
  23530. }
  23531. var segment = playlist.segments[0];
  23532. if (dateTimeObject < segment.dateTimeObject) {
  23533. // Requested time is before stream start.
  23534. return null;
  23535. }
  23536. for (var i = 0; i < playlist.segments.length - 1; i++) {
  23537. segment = playlist.segments[i];
  23538. var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
  23539. if (dateTimeObject < nextSegmentStart) {
  23540. break;
  23541. }
  23542. }
  23543. var lastSegment = playlist.segments[playlist.segments.length - 1];
  23544. var lastSegmentStart = lastSegment.dateTimeObject;
  23545. var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
  23546. var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
  23547. if (dateTimeObject > lastSegmentEnd) {
  23548. // Beyond the end of the stream, or our best guess of the end of the stream.
  23549. return null;
  23550. }
  23551. if (dateTimeObject > lastSegmentStart) {
  23552. segment = lastSegment;
  23553. }
  23554. return {
  23555. segment: segment,
  23556. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
  23557. // Although, given that all segments have accurate date time objects, the segment
  23558. // selected should be accurate, unless the video has been transmuxed at some point
  23559. // (determined by the presence of the videoTimingInfo object), the segment's "player
  23560. // time" (the start time in the player) can't be considered accurate.
  23561. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  23562. };
  23563. };
  23564. /**
  23565. * Finds a segment that contains the given player time(in seconds).
  23566. *
  23567. * @param {Number} time The player time to find a match for
  23568. * @param {Object} playlist A playlist object to search within
  23569. */
  23570. var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
  23571. // Assumptions:
  23572. // - there will always be a segment.duration
  23573. // - we can start from zero
  23574. // - segments are in time order
  23575. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  23576. return null;
  23577. }
  23578. var segmentEnd = 0;
  23579. var segment = void 0;
  23580. for (var i = 0; i < playlist.segments.length; i++) {
  23581. segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
  23582. // should contain the most accurate values we have for the segment's player times.
  23583. //
  23584. // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
  23585. // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
  23586. // calculate an end value.
  23587. segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
  23588. if (time <= segmentEnd) {
  23589. break;
  23590. }
  23591. }
  23592. var lastSegment = playlist.segments[playlist.segments.length - 1];
  23593. if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
  23594. // The time requested is beyond the stream end.
  23595. return null;
  23596. }
  23597. if (time > segmentEnd) {
  23598. // The time is within or beyond the last segment.
  23599. //
  23600. // Check to see if the time is beyond a reasonable guess of the end of the stream.
  23601. if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
  23602. // Technically, because the duration value is only an estimate, the time may still
  23603. // exist in the last segment, however, there isn't enough information to make even
  23604. // a reasonable estimate.
  23605. return null;
  23606. }
  23607. segment = lastSegment;
  23608. }
  23609. return {
  23610. segment: segment,
  23611. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
  23612. // Because videoTimingInfo is only set after transmux, it is the only way to get
  23613. // accurate timing values.
  23614. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  23615. };
  23616. };
  23617. /**
  23618. * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
  23619. * If the offset returned is positive, the programTime occurs after the
  23620. * comparisonTimestamp.
  23621. * If the offset is negative, the programTime occurs before the comparisonTimestamp.
  23622. *
  23623. * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against
  23624. * @param {String} programTime The programTime as an ISO-8601 string
  23625. * @return {Number} offset
  23626. */
  23627. var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
  23628. var segmentDateTime = void 0;
  23629. var programDateTime = void 0;
  23630. try {
  23631. segmentDateTime = new Date(comparisonTimeStamp);
  23632. programDateTime = new Date(programTime);
  23633. } catch (e) {// TODO handle error
  23634. }
  23635. var segmentTimeEpoch = segmentDateTime.getTime();
  23636. var programTimeEpoch = programDateTime.getTime();
  23637. return (programTimeEpoch - segmentTimeEpoch) / 1000;
  23638. };
  23639. /**
  23640. * Checks that all segments in this playlist have programDateTime tags.
  23641. *
  23642. * @param {Object} playlist A playlist object
  23643. */
  23644. var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
  23645. if (!playlist.segments || playlist.segments.length === 0) {
  23646. return false;
  23647. }
  23648. for (var i = 0; i < playlist.segments.length; i++) {
  23649. var segment = playlist.segments[i];
  23650. if (!segment.dateTimeObject) {
  23651. return false;
  23652. }
  23653. }
  23654. return true;
  23655. };
  23656. /**
  23657. * Returns the programTime of the media given a playlist and a playerTime.
  23658. * The playlist must have programDateTime tags for a programDateTime tag to be returned.
  23659. * If the segments containing the time requested have not been buffered yet, an estimate
  23660. * may be returned to the callback.
  23661. *
  23662. * @param {Object} args
  23663. * @param {Object} args.playlist A playlist object to search within
  23664. * @param {Number} time A playerTime in seconds
  23665. * @param {Function} callback(err, programTime)
  23666. * @returns {String} err.message A detailed error message
  23667. * @returns {Object} programTime
  23668. * @returns {Number} programTime.mediaSeconds The streamTime in seconds
  23669. * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String
  23670. */
  23671. var getProgramTime = function getProgramTime(_ref) {
  23672. var playlist = _ref.playlist,
  23673. _ref$time = _ref.time,
  23674. time = _ref$time === undefined ? undefined : _ref$time,
  23675. callback = _ref.callback;
  23676. if (!callback) {
  23677. throw new Error('getProgramTime: callback must be provided');
  23678. }
  23679. if (!playlist || time === undefined) {
  23680. return callback({
  23681. message: 'getProgramTime: playlist and time must be provided'
  23682. });
  23683. }
  23684. var matchedSegment = findSegmentForPlayerTime(time, playlist);
  23685. if (!matchedSegment) {
  23686. return callback({
  23687. message: 'valid programTime was not found'
  23688. });
  23689. }
  23690. if (matchedSegment.type === 'estimate') {
  23691. return callback({
  23692. message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
  23693. seekTime: matchedSegment.estimatedStart
  23694. });
  23695. }
  23696. var programTimeObject = {
  23697. mediaSeconds: time
  23698. };
  23699. var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
  23700. if (programTime) {
  23701. programTimeObject.programDateTime = programTime.toISOString();
  23702. }
  23703. return callback(null, programTimeObject);
  23704. };
  23705. /**
  23706. * Seeks in the player to a time that matches the given programTime ISO-8601 string.
  23707. *
  23708. * @param {Object} args
  23709. * @param {String} args.programTime A programTime to seek to as an ISO-8601 String
  23710. * @param {Object} args.playlist A playlist to look within
  23711. * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2.
  23712. * @param {Function} args.seekTo A method to perform a seek
  23713. * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
  23714. * @param {Object} args.tech The tech to seek on
  23715. * @param {Function} args.callback(err, newTime) A callback to return the new time to
  23716. * @returns {String} err.message A detailed error message
  23717. * @returns {Number} newTime The exact time that was seeked to in seconds
  23718. */
  23719. var seekToProgramTime = function seekToProgramTime(_ref2) {
  23720. var programTime = _ref2.programTime,
  23721. playlist = _ref2.playlist,
  23722. _ref2$retryCount = _ref2.retryCount,
  23723. retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount,
  23724. seekTo = _ref2.seekTo,
  23725. _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
  23726. pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek,
  23727. tech = _ref2.tech,
  23728. callback = _ref2.callback;
  23729. if (!callback) {
  23730. throw new Error('seekToProgramTime: callback must be provided');
  23731. }
  23732. if (typeof programTime === 'undefined' || !playlist || !seekTo) {
  23733. return callback({
  23734. message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
  23735. });
  23736. }
  23737. if (!playlist.endList && !tech.hasStarted_) {
  23738. return callback({
  23739. message: 'player must be playing a live stream to start buffering'
  23740. });
  23741. }
  23742. if (!verifyProgramDateTimeTags(playlist)) {
  23743. return callback({
  23744. message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
  23745. });
  23746. }
  23747. var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
  23748. if (!matchedSegment) {
  23749. return callback({
  23750. message: programTime + ' was not found in the stream'
  23751. });
  23752. }
  23753. var segment = matchedSegment.segment;
  23754. var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
  23755. if (matchedSegment.type === 'estimate') {
  23756. // we've run out of retries
  23757. if (retryCount === 0) {
  23758. return callback({
  23759. message: programTime + ' is not buffered yet. Try again'
  23760. });
  23761. }
  23762. seekTo(matchedSegment.estimatedStart + mediaOffset);
  23763. tech.one('seeked', function () {
  23764. seekToProgramTime({
  23765. programTime: programTime,
  23766. playlist: playlist,
  23767. retryCount: retryCount - 1,
  23768. seekTo: seekTo,
  23769. pauseAfterSeek: pauseAfterSeek,
  23770. tech: tech,
  23771. callback: callback
  23772. });
  23773. });
  23774. return;
  23775. } // Since the segment.start value is determined from the buffered end or ending time
  23776. // of the prior segment, the seekToTime doesn't need to account for any transmuxer
  23777. // modifications.
  23778. var seekToTime = segment.start + mediaOffset;
  23779. var seekedCallback = function seekedCallback() {
  23780. return callback(null, tech.currentTime());
  23781. }; // listen for seeked event
  23782. tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
  23783. if (pauseAfterSeek) {
  23784. tech.pause();
  23785. }
  23786. seekTo(seekToTime);
  23787. };
  23788. /**
  23789. * ranges
  23790. *
  23791. * Utilities for working with TimeRanges.
  23792. *
  23793. */
  23794. // Fudge factor to account for TimeRanges rounding
  23795. var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
  23796. // can be misleading because of precision differences or when the current media has poorly
  23797. // aligned audio and video, which can cause values to be slightly off from what you would
  23798. // expect. This value is what we consider to be safe to use in such comparisons to account
  23799. // for these scenarios.
  23800. var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
  23801. var filterRanges = function filterRanges(timeRanges, predicate) {
  23802. var results = [];
  23803. var i = void 0;
  23804. if (timeRanges && timeRanges.length) {
  23805. // Search for ranges that match the predicate
  23806. for (i = 0; i < timeRanges.length; i++) {
  23807. if (predicate(timeRanges.start(i), timeRanges.end(i))) {
  23808. results.push([timeRanges.start(i), timeRanges.end(i)]);
  23809. }
  23810. }
  23811. }
  23812. return videojs$1.createTimeRanges(results);
  23813. };
  23814. /**
  23815. * Attempts to find the buffered TimeRange that contains the specified
  23816. * time.
  23817. * @param {TimeRanges} buffered - the TimeRanges object to query
  23818. * @param {number} time - the time to filter on.
  23819. * @returns {TimeRanges} a new TimeRanges object
  23820. */
  23821. var findRange = function findRange(buffered, time) {
  23822. return filterRanges(buffered, function (start, end) {
  23823. return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
  23824. });
  23825. };
  23826. /**
  23827. * Returns the TimeRanges that begin later than the specified time.
  23828. * @param {TimeRanges} timeRanges - the TimeRanges object to query
  23829. * @param {number} time - the time to filter on.
  23830. * @returns {TimeRanges} a new TimeRanges object.
  23831. */
  23832. var findNextRange = function findNextRange(timeRanges, time) {
  23833. return filterRanges(timeRanges, function (start) {
  23834. return start - TIME_FUDGE_FACTOR >= time;
  23835. });
  23836. };
  23837. /**
  23838. * Returns gaps within a list of TimeRanges
  23839. * @param {TimeRanges} buffered - the TimeRanges object
  23840. * @return {TimeRanges} a TimeRanges object of gaps
  23841. */
  23842. var findGaps = function findGaps(buffered) {
  23843. if (buffered.length < 2) {
  23844. return videojs$1.createTimeRanges();
  23845. }
  23846. var ranges = [];
  23847. for (var i = 1; i < buffered.length; i++) {
  23848. var start = buffered.end(i - 1);
  23849. var end = buffered.start(i);
  23850. ranges.push([start, end]);
  23851. }
  23852. return videojs$1.createTimeRanges(ranges);
  23853. };
  23854. /**
  23855. * Gets a human readable string for a TimeRange
  23856. *
  23857. * @param {TimeRange} range
  23858. * @returns {String} a human readable string
  23859. */
  23860. var printableRange = function printableRange(range) {
  23861. var strArr = [];
  23862. if (!range || !range.length) {
  23863. return '';
  23864. }
  23865. for (var i = 0; i < range.length; i++) {
  23866. strArr.push(range.start(i) + ' => ' + range.end(i));
  23867. }
  23868. return strArr.join(', ');
  23869. };
  23870. /**
  23871. * Calculates the amount of time left in seconds until the player hits the end of the
  23872. * buffer and causes a rebuffer
  23873. *
  23874. * @param {TimeRange} buffered
  23875. * The state of the buffer
  23876. * @param {Numnber} currentTime
  23877. * The current time of the player
  23878. * @param {Number} playbackRate
  23879. * The current playback rate of the player. Defaults to 1.
  23880. * @return {Number}
  23881. * Time until the player has to start rebuffering in seconds.
  23882. * @function timeUntilRebuffer
  23883. */
  23884. var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
  23885. var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
  23886. var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
  23887. return (bufferedEnd - currentTime) / playbackRate;
  23888. };
  23889. /**
  23890. * Converts a TimeRanges object into an array representation
  23891. * @param {TimeRanges} timeRanges
  23892. * @returns {Array}
  23893. */
  23894. var timeRangesToArray = function timeRangesToArray(timeRanges) {
  23895. var timeRangesList = [];
  23896. for (var i = 0; i < timeRanges.length; i++) {
  23897. timeRangesList.push({
  23898. start: timeRanges.start(i),
  23899. end: timeRanges.end(i)
  23900. });
  23901. }
  23902. return timeRangesList;
  23903. };
  23904. /**
  23905. * @file create-text-tracks-if-necessary.js
  23906. */
  23907. /**
  23908. * Create text tracks on video.js if they exist on a segment.
  23909. *
  23910. * @param {Object} sourceBuffer the VSB or FSB
  23911. * @param {Object} mediaSource the HTML media source
  23912. * @param {Object} segment the segment that may contain the text track
  23913. * @private
  23914. */
  23915. var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
  23916. var player = mediaSource.player_; // create an in-band caption track if one is present in the segment
  23917. if (segment.captions && segment.captions.length) {
  23918. if (!sourceBuffer.inbandTextTracks_) {
  23919. sourceBuffer.inbandTextTracks_ = {};
  23920. }
  23921. for (var trackId in segment.captionStreams) {
  23922. if (!sourceBuffer.inbandTextTracks_[trackId]) {
  23923. player.tech_.trigger({
  23924. type: 'usage',
  23925. name: 'hls-608'
  23926. });
  23927. var track = player.textTracks().getTrackById(trackId);
  23928. if (track) {
  23929. // Resuse an existing track with a CC# id because this was
  23930. // very likely created by videojs-contrib-hls from information
  23931. // in the m3u8 for us to use
  23932. sourceBuffer.inbandTextTracks_[trackId] = track;
  23933. } else {
  23934. // Otherwise, create a track with the default `CC#` label and
  23935. // without a language
  23936. sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
  23937. kind: 'captions',
  23938. id: trackId,
  23939. label: trackId
  23940. }, false).track;
  23941. }
  23942. }
  23943. }
  23944. }
  23945. if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
  23946. sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
  23947. kind: 'metadata',
  23948. label: 'Timed Metadata'
  23949. }, false).track;
  23950. sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
  23951. }
  23952. };
  23953. /**
  23954. * @file remove-cues-from-track.js
  23955. */
  23956. /**
  23957. * Remove cues from a track on video.js.
  23958. *
  23959. * @param {Double} start start of where we should remove the cue
  23960. * @param {Double} end end of where the we should remove the cue
  23961. * @param {Object} track the text track to remove the cues from
  23962. * @private
  23963. */
  23964. var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
  23965. var i = void 0;
  23966. var cue = void 0;
  23967. if (!track) {
  23968. return;
  23969. }
  23970. if (!track.cues) {
  23971. return;
  23972. }
  23973. i = track.cues.length;
  23974. while (i--) {
  23975. cue = track.cues[i]; // Remove any overlapping cue
  23976. if (cue.startTime <= end && cue.endTime >= start) {
  23977. track.removeCue(cue);
  23978. }
  23979. }
  23980. };
  23981. /**
  23982. * @file add-text-track-data.js
  23983. */
  23984. /**
  23985. * Define properties on a cue for backwards compatability,
  23986. * but warn the user that the way that they are using it
  23987. * is depricated and will be removed at a later date.
  23988. *
  23989. * @param {Cue} cue the cue to add the properties on
  23990. * @private
  23991. */
  23992. var deprecateOldCue = function deprecateOldCue(cue) {
  23993. Object.defineProperties(cue.frame, {
  23994. id: {
  23995. get: function get() {
  23996. videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
  23997. return cue.value.key;
  23998. }
  23999. },
  24000. value: {
  24001. get: function get() {
  24002. videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
  24003. return cue.value.data;
  24004. }
  24005. },
  24006. privateData: {
  24007. get: function get() {
  24008. videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
  24009. return cue.value.data;
  24010. }
  24011. }
  24012. });
  24013. };
  24014. var durationOfVideo = function durationOfVideo(duration) {
  24015. var dur = void 0;
  24016. if (isNaN(duration) || Math.abs(duration) === Infinity) {
  24017. dur = Number.MAX_VALUE;
  24018. } else {
  24019. dur = duration;
  24020. }
  24021. return dur;
  24022. };
  24023. /**
  24024. * Add text track data to a source handler given the captions and
  24025. * metadata from the buffer.
  24026. *
  24027. * @param {Object} sourceHandler the virtual source buffer
  24028. * @param {Array} captionArray an array of caption data
  24029. * @param {Array} metadataArray an array of meta data
  24030. * @private
  24031. */
  24032. var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
  24033. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  24034. if (captionArray) {
  24035. captionArray.forEach(function (caption) {
  24036. var track = caption.stream;
  24037. this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
  24038. }, sourceHandler);
  24039. }
  24040. if (metadataArray) {
  24041. var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
  24042. metadataArray.forEach(function (metadata) {
  24043. var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
  24044. // ignore this bit of metadata.
  24045. // This likely occurs when you have an non-timed ID3 tag like TIT2,
  24046. // which is the "Title/Songname/Content description" frame
  24047. if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
  24048. return;
  24049. }
  24050. metadata.frames.forEach(function (frame) {
  24051. var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
  24052. cue.frame = frame;
  24053. cue.value = frame;
  24054. deprecateOldCue(cue);
  24055. this.metadataTrack_.addCue(cue);
  24056. }, this);
  24057. }, sourceHandler); // Updating the metadeta cues so that
  24058. // the endTime of each cue is the startTime of the next cue
  24059. // the endTime of last cue is the duration of the video
  24060. if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
  24061. var cues = sourceHandler.metadataTrack_.cues;
  24062. var cuesArray = []; // Create a copy of the TextTrackCueList...
  24063. // ...disregarding cues with a falsey value
  24064. for (var i = 0; i < cues.length; i++) {
  24065. if (cues[i]) {
  24066. cuesArray.push(cues[i]);
  24067. }
  24068. } // Group cues by their startTime value
  24069. var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
  24070. var timeSlot = obj[cue.startTime] || [];
  24071. timeSlot.push(cue);
  24072. obj[cue.startTime] = timeSlot;
  24073. return obj;
  24074. }, {}); // Sort startTimes by ascending order
  24075. var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
  24076. return Number(a) - Number(b);
  24077. }); // Map each cue group's endTime to the next group's startTime
  24078. sortedStartTimes.forEach(function (startTime, idx) {
  24079. var cueGroup = cuesGroupedByStartTime[startTime];
  24080. var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
  24081. cueGroup.forEach(function (cue) {
  24082. cue.endTime = nextTime;
  24083. });
  24084. });
  24085. }
  24086. }
  24087. };
  24088. var win = typeof window !== 'undefined' ? window : {},
  24089. TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
  24090. SCRIPT_TYPE = 'application/javascript',
  24091. BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder,
  24092. URL = win.URL || win.webkitURL || URL && URL.msURL,
  24093. Worker = win.Worker;
  24094. /**
  24095. * Returns a wrapper around Web Worker code that is constructible.
  24096. *
  24097. * @function shimWorker
  24098. *
  24099. * @param { String } filename The name of the file
  24100. * @param { Function } fn Function wrapping the code of the worker
  24101. */
  24102. function shimWorker(filename, fn) {
  24103. return function ShimWorker(forceFallback) {
  24104. var o = this;
  24105. if (!fn) {
  24106. return new Worker(filename);
  24107. } else if (Worker && !forceFallback) {
  24108. // Convert the function's inner code to a string to construct the worker
  24109. var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
  24110. objURL = createSourceObject(source);
  24111. this[TARGET] = new Worker(objURL);
  24112. wrapTerminate(this[TARGET], objURL);
  24113. return this[TARGET];
  24114. } else {
  24115. var selfShim = {
  24116. postMessage: function postMessage(m) {
  24117. if (o.onmessage) {
  24118. setTimeout(function () {
  24119. o.onmessage({
  24120. data: m,
  24121. target: selfShim
  24122. });
  24123. });
  24124. }
  24125. }
  24126. };
  24127. fn.call(selfShim);
  24128. this.postMessage = function (m) {
  24129. setTimeout(function () {
  24130. selfShim.onmessage({
  24131. data: m,
  24132. target: o
  24133. });
  24134. });
  24135. };
  24136. this.isThisThread = true;
  24137. }
  24138. };
  24139. } // Test Worker capabilities
  24140. if (Worker) {
  24141. var testWorker,
  24142. objURL = createSourceObject('self.onmessage = function () {}'),
  24143. testArray = new Uint8Array(1);
  24144. try {
  24145. testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it
  24146. testWorker.postMessage(testArray, [testArray.buffer]);
  24147. } catch (e) {
  24148. Worker = null;
  24149. } finally {
  24150. URL.revokeObjectURL(objURL);
  24151. if (testWorker) {
  24152. testWorker.terminate();
  24153. }
  24154. }
  24155. }
  24156. function createSourceObject(str) {
  24157. try {
  24158. return URL.createObjectURL(new Blob([str], {
  24159. type: SCRIPT_TYPE
  24160. }));
  24161. } catch (e) {
  24162. var blob = new BlobBuilder();
  24163. blob.append(str);
  24164. return URL.createObjectURL(blob.getBlob(type));
  24165. }
  24166. }
  24167. function wrapTerminate(worker, objURL) {
  24168. if (!worker || !objURL) return;
  24169. var term = worker.terminate;
  24170. worker.objURL = objURL;
  24171. worker.terminate = function () {
  24172. if (worker.objURL) URL.revokeObjectURL(worker.objURL);
  24173. term.call(worker);
  24174. };
  24175. }
  24176. var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
  24177. var self = this;
  24178. var transmuxerWorker = function () {
  24179. /**
  24180. * mux.js
  24181. *
  24182. * Copyright (c) 2015 Brightcove
  24183. * All rights reserved.
  24184. *
  24185. * Functions that generate fragmented MP4s suitable for use with Media
  24186. * Source Extensions.
  24187. */
  24188. var UINT32_MAX = Math.pow(2, 32) - 1;
  24189. 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
  24190. (function () {
  24191. var i;
  24192. types = {
  24193. avc1: [],
  24194. // codingname
  24195. avcC: [],
  24196. btrt: [],
  24197. dinf: [],
  24198. dref: [],
  24199. esds: [],
  24200. ftyp: [],
  24201. hdlr: [],
  24202. mdat: [],
  24203. mdhd: [],
  24204. mdia: [],
  24205. mfhd: [],
  24206. minf: [],
  24207. moof: [],
  24208. moov: [],
  24209. mp4a: [],
  24210. // codingname
  24211. mvex: [],
  24212. mvhd: [],
  24213. sdtp: [],
  24214. smhd: [],
  24215. stbl: [],
  24216. stco: [],
  24217. stsc: [],
  24218. stsd: [],
  24219. stsz: [],
  24220. stts: [],
  24221. styp: [],
  24222. tfdt: [],
  24223. tfhd: [],
  24224. traf: [],
  24225. trak: [],
  24226. trun: [],
  24227. trex: [],
  24228. tkhd: [],
  24229. vmhd: []
  24230. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  24231. // don't throw an error
  24232. if (typeof Uint8Array === 'undefined') {
  24233. return;
  24234. }
  24235. for (i in types) {
  24236. if (types.hasOwnProperty(i)) {
  24237. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  24238. }
  24239. }
  24240. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  24241. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  24242. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  24243. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  24244. 0x00, 0x00, 0x00, // flags
  24245. 0x00, 0x00, 0x00, 0x00, // pre_defined
  24246. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  24247. 0x00, 0x00, 0x00, 0x00, // reserved
  24248. 0x00, 0x00, 0x00, 0x00, // reserved
  24249. 0x00, 0x00, 0x00, 0x00, // reserved
  24250. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  24251. ]);
  24252. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  24253. 0x00, 0x00, 0x00, // flags
  24254. 0x00, 0x00, 0x00, 0x00, // pre_defined
  24255. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  24256. 0x00, 0x00, 0x00, 0x00, // reserved
  24257. 0x00, 0x00, 0x00, 0x00, // reserved
  24258. 0x00, 0x00, 0x00, 0x00, // reserved
  24259. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  24260. ]);
  24261. HDLR_TYPES = {
  24262. video: VIDEO_HDLR,
  24263. audio: AUDIO_HDLR
  24264. };
  24265. DREF = new Uint8Array([0x00, // version 0
  24266. 0x00, 0x00, 0x00, // flags
  24267. 0x00, 0x00, 0x00, 0x01, // entry_count
  24268. 0x00, 0x00, 0x00, 0x0c, // entry_size
  24269. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  24270. 0x00, // version 0
  24271. 0x00, 0x00, 0x01 // entry_flags
  24272. ]);
  24273. SMHD = new Uint8Array([0x00, // version
  24274. 0x00, 0x00, 0x00, // flags
  24275. 0x00, 0x00, // balance, 0 means centered
  24276. 0x00, 0x00 // reserved
  24277. ]);
  24278. STCO = new Uint8Array([0x00, // version
  24279. 0x00, 0x00, 0x00, // flags
  24280. 0x00, 0x00, 0x00, 0x00 // entry_count
  24281. ]);
  24282. STSC = STCO;
  24283. STSZ = new Uint8Array([0x00, // version
  24284. 0x00, 0x00, 0x00, // flags
  24285. 0x00, 0x00, 0x00, 0x00, // sample_size
  24286. 0x00, 0x00, 0x00, 0x00 // sample_count
  24287. ]);
  24288. STTS = STCO;
  24289. VMHD = new Uint8Array([0x00, // version
  24290. 0x00, 0x00, 0x01, // flags
  24291. 0x00, 0x00, // graphicsmode
  24292. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  24293. ]);
  24294. })();
  24295. box = function box(type) {
  24296. var payload = [],
  24297. size = 0,
  24298. i,
  24299. result,
  24300. view;
  24301. for (i = 1; i < arguments.length; i++) {
  24302. payload.push(arguments[i]);
  24303. }
  24304. i = payload.length; // calculate the total size we need to allocate
  24305. while (i--) {
  24306. size += payload[i].byteLength;
  24307. }
  24308. result = new Uint8Array(size + 8);
  24309. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  24310. view.setUint32(0, result.byteLength);
  24311. result.set(type, 4); // copy the payload into the result
  24312. for (i = 0, size = 8; i < payload.length; i++) {
  24313. result.set(payload[i], size);
  24314. size += payload[i].byteLength;
  24315. }
  24316. return result;
  24317. };
  24318. dinf = function dinf() {
  24319. return box(types.dinf, box(types.dref, DREF));
  24320. };
  24321. esds = function esds(track) {
  24322. return box(types.esds, new Uint8Array([0x00, // version
  24323. 0x00, 0x00, 0x00, // flags
  24324. // ES_Descriptor
  24325. 0x03, // tag, ES_DescrTag
  24326. 0x19, // length
  24327. 0x00, 0x00, // ES_ID
  24328. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  24329. // DecoderConfigDescriptor
  24330. 0x04, // tag, DecoderConfigDescrTag
  24331. 0x11, // length
  24332. 0x40, // object type
  24333. 0x15, // streamType
  24334. 0x00, 0x06, 0x00, // bufferSizeDB
  24335. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  24336. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  24337. // DecoderSpecificInfo
  24338. 0x05, // tag, DecoderSpecificInfoTag
  24339. 0x02, // length
  24340. // ISO/IEC 14496-3, AudioSpecificConfig
  24341. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  24342. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  24343. ]));
  24344. };
  24345. ftyp = function ftyp() {
  24346. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  24347. };
  24348. hdlr = function hdlr(type) {
  24349. return box(types.hdlr, HDLR_TYPES[type]);
  24350. };
  24351. mdat = function mdat(data) {
  24352. return box(types.mdat, data);
  24353. };
  24354. mdhd = function mdhd(track) {
  24355. var result = new Uint8Array([0x00, // version 0
  24356. 0x00, 0x00, 0x00, // flags
  24357. 0x00, 0x00, 0x00, 0x02, // creation_time
  24358. 0x00, 0x00, 0x00, 0x03, // modification_time
  24359. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  24360. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  24361. 0x55, 0xc4, // 'und' language (undetermined)
  24362. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  24363. // defined. The sample rate can be parsed out of an ADTS header, for
  24364. // instance.
  24365. if (track.samplerate) {
  24366. result[12] = track.samplerate >>> 24 & 0xFF;
  24367. result[13] = track.samplerate >>> 16 & 0xFF;
  24368. result[14] = track.samplerate >>> 8 & 0xFF;
  24369. result[15] = track.samplerate & 0xFF;
  24370. }
  24371. return box(types.mdhd, result);
  24372. };
  24373. mdia = function mdia(track) {
  24374. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  24375. };
  24376. mfhd = function mfhd(sequenceNumber) {
  24377. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  24378. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  24379. ]));
  24380. };
  24381. minf = function minf(track) {
  24382. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  24383. };
  24384. moof = function moof(sequenceNumber, tracks) {
  24385. var trackFragments = [],
  24386. i = tracks.length; // build traf boxes for each track fragment
  24387. while (i--) {
  24388. trackFragments[i] = traf(tracks[i]);
  24389. }
  24390. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  24391. };
  24392. /**
  24393. * Returns a movie box.
  24394. * @param tracks {array} the tracks associated with this movie
  24395. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  24396. */
  24397. moov = function moov(tracks) {
  24398. var i = tracks.length,
  24399. boxes = [];
  24400. while (i--) {
  24401. boxes[i] = trak(tracks[i]);
  24402. }
  24403. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  24404. };
  24405. mvex = function mvex(tracks) {
  24406. var i = tracks.length,
  24407. boxes = [];
  24408. while (i--) {
  24409. boxes[i] = trex(tracks[i]);
  24410. }
  24411. return box.apply(null, [types.mvex].concat(boxes));
  24412. };
  24413. mvhd = function mvhd(duration) {
  24414. var bytes = new Uint8Array([0x00, // version 0
  24415. 0x00, 0x00, 0x00, // flags
  24416. 0x00, 0x00, 0x00, 0x01, // creation_time
  24417. 0x00, 0x00, 0x00, 0x02, // modification_time
  24418. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  24419. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  24420. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  24421. 0x01, 0x00, // 1.0 volume
  24422. 0x00, 0x00, // reserved
  24423. 0x00, 0x00, 0x00, 0x00, // reserved
  24424. 0x00, 0x00, 0x00, 0x00, // reserved
  24425. 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
  24426. 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
  24427. 0xff, 0xff, 0xff, 0xff // next_track_ID
  24428. ]);
  24429. return box(types.mvhd, bytes);
  24430. };
  24431. sdtp = function sdtp(track) {
  24432. var samples = track.samples || [],
  24433. bytes = new Uint8Array(4 + samples.length),
  24434. flags,
  24435. i; // leave the full box header (4 bytes) all zero
  24436. // write the sample table
  24437. for (i = 0; i < samples.length; i++) {
  24438. flags = samples[i].flags;
  24439. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  24440. }
  24441. return box(types.sdtp, bytes);
  24442. };
  24443. stbl = function stbl(track) {
  24444. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  24445. };
  24446. (function () {
  24447. var videoSample, audioSample;
  24448. stsd = function stsd(track) {
  24449. return box(types.stsd, new Uint8Array([0x00, // version 0
  24450. 0x00, 0x00, 0x00, // flags
  24451. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  24452. };
  24453. videoSample = function videoSample(track) {
  24454. var sps = track.sps || [],
  24455. pps = track.pps || [],
  24456. sequenceParameterSets = [],
  24457. pictureParameterSets = [],
  24458. i; // assemble the SPSs
  24459. for (i = 0; i < sps.length; i++) {
  24460. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  24461. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  24462. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  24463. } // assemble the PPSs
  24464. for (i = 0; i < pps.length; i++) {
  24465. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  24466. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  24467. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  24468. }
  24469. return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  24470. 0x00, 0x01, // data_reference_index
  24471. 0x00, 0x00, // pre_defined
  24472. 0x00, 0x00, // reserved
  24473. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  24474. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  24475. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  24476. 0x00, 0x48, 0x00, 0x00, // horizresolution
  24477. 0x00, 0x48, 0x00, 0x00, // vertresolution
  24478. 0x00, 0x00, 0x00, 0x00, // reserved
  24479. 0x00, 0x01, // frame_count
  24480. 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
  24481. 0x00, 0x18, // depth = 24
  24482. 0x11, 0x11 // pre_defined = -1
  24483. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  24484. track.profileIdc, // AVCProfileIndication
  24485. track.profileCompatibility, // profile_compatibility
  24486. track.levelIdc, // AVCLevelIndication
  24487. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  24488. ].concat([sps.length // numOfSequenceParameterSets
  24489. ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
  24490. ]).concat(pictureParameterSets))), // "PPS"
  24491. box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  24492. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  24493. 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
  24494. );
  24495. };
  24496. audioSample = function audioSample(track) {
  24497. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  24498. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  24499. 0x00, 0x01, // data_reference_index
  24500. // AudioSampleEntry, ISO/IEC 14496-12
  24501. 0x00, 0x00, 0x00, 0x00, // reserved
  24502. 0x00, 0x00, 0x00, 0x00, // reserved
  24503. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  24504. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  24505. 0x00, 0x00, // pre_defined
  24506. 0x00, 0x00, // reserved
  24507. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  24508. // MP4AudioSampleEntry, ISO/IEC 14496-14
  24509. ]), esds(track));
  24510. };
  24511. })();
  24512. tkhd = function tkhd(track) {
  24513. var result = new Uint8Array([0x00, // version 0
  24514. 0x00, 0x00, 0x07, // flags
  24515. 0x00, 0x00, 0x00, 0x00, // creation_time
  24516. 0x00, 0x00, 0x00, 0x00, // modification_time
  24517. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  24518. 0x00, 0x00, 0x00, 0x00, // reserved
  24519. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  24520. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  24521. 0x00, 0x00, // layer
  24522. 0x00, 0x00, // alternate_group
  24523. 0x01, 0x00, // non-audio track volume
  24524. 0x00, 0x00, // reserved
  24525. 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
  24526. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  24527. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  24528. ]);
  24529. return box(types.tkhd, result);
  24530. };
  24531. /**
  24532. * Generate a track fragment (traf) box. A traf box collects metadata
  24533. * about tracks in a movie fragment (moof) box.
  24534. */
  24535. traf = function traf(track) {
  24536. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  24537. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  24538. 0x00, 0x00, 0x3a, // flags
  24539. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  24540. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  24541. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  24542. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  24543. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  24544. ]));
  24545. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
  24546. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
  24547. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  24548. 0x00, 0x00, 0x00, // flags
  24549. // baseMediaDecodeTime
  24550. 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
  24551. // the containing moof to the first payload byte of the associated
  24552. // mdat
  24553. dataOffset = 32 + // tfhd
  24554. 20 + // tfdt
  24555. 8 + // traf header
  24556. 16 + // mfhd
  24557. 8 + // moof header
  24558. 8; // mdat header
  24559. // audio tracks require less metadata
  24560. if (track.type === 'audio') {
  24561. trackFragmentRun = trun(track, dataOffset);
  24562. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  24563. } // video tracks should contain an independent and disposable samples
  24564. // box (sdtp)
  24565. // generate one and adjust offsets to match
  24566. sampleDependencyTable = sdtp(track);
  24567. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  24568. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  24569. };
  24570. /**
  24571. * Generate a track box.
  24572. * @param track {object} a track definition
  24573. * @return {Uint8Array} the track box
  24574. */
  24575. trak = function trak(track) {
  24576. track.duration = track.duration || 0xffffffff;
  24577. return box(types.trak, tkhd(track), mdia(track));
  24578. };
  24579. trex = function trex(track) {
  24580. var result = new Uint8Array([0x00, // version 0
  24581. 0x00, 0x00, 0x00, // flags
  24582. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  24583. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  24584. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  24585. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  24586. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  24587. ]); // the last two bytes of default_sample_flags is the sample
  24588. // degradation priority, a hint about the importance of this sample
  24589. // relative to others. Lower the degradation priority for all sample
  24590. // types other than video.
  24591. if (track.type !== 'video') {
  24592. result[result.length - 1] = 0x00;
  24593. }
  24594. return box(types.trex, result);
  24595. };
  24596. (function () {
  24597. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  24598. // duration is present for the first sample, it will be present for
  24599. // all subsequent samples.
  24600. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  24601. trunHeader = function trunHeader(samples, offset) {
  24602. var durationPresent = 0,
  24603. sizePresent = 0,
  24604. flagsPresent = 0,
  24605. compositionTimeOffset = 0; // trun flag constants
  24606. if (samples.length) {
  24607. if (samples[0].duration !== undefined) {
  24608. durationPresent = 0x1;
  24609. }
  24610. if (samples[0].size !== undefined) {
  24611. sizePresent = 0x2;
  24612. }
  24613. if (samples[0].flags !== undefined) {
  24614. flagsPresent = 0x4;
  24615. }
  24616. if (samples[0].compositionTimeOffset !== undefined) {
  24617. compositionTimeOffset = 0x8;
  24618. }
  24619. }
  24620. return [0x00, // version 0
  24621. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  24622. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  24623. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  24624. ];
  24625. };
  24626. videoTrun = function videoTrun(track, offset) {
  24627. var bytes, samples, sample, i;
  24628. samples = track.samples || [];
  24629. offset += 8 + 12 + 16 * samples.length;
  24630. bytes = trunHeader(samples, offset);
  24631. for (i = 0; i < samples.length; i++) {
  24632. sample = samples[i];
  24633. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  24634. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
  24635. 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
  24636. (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
  24637. ]);
  24638. }
  24639. return box(types.trun, new Uint8Array(bytes));
  24640. };
  24641. audioTrun = function audioTrun(track, offset) {
  24642. var bytes, samples, sample, i;
  24643. samples = track.samples || [];
  24644. offset += 8 + 12 + 8 * samples.length;
  24645. bytes = trunHeader(samples, offset);
  24646. for (i = 0; i < samples.length; i++) {
  24647. sample = samples[i];
  24648. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  24649. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
  24650. }
  24651. return box(types.trun, new Uint8Array(bytes));
  24652. };
  24653. trun = function trun(track, offset) {
  24654. if (track.type === 'audio') {
  24655. return audioTrun(track, offset);
  24656. }
  24657. return videoTrun(track, offset);
  24658. };
  24659. })();
  24660. var mp4Generator = {
  24661. ftyp: ftyp,
  24662. mdat: mdat,
  24663. moof: moof,
  24664. moov: moov,
  24665. initSegment: function initSegment(tracks) {
  24666. var fileType = ftyp(),
  24667. movie = moov(tracks),
  24668. result;
  24669. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  24670. result.set(fileType);
  24671. result.set(movie, fileType.byteLength);
  24672. return result;
  24673. }
  24674. };
  24675. var toUnsigned = function toUnsigned(value) {
  24676. return value >>> 0;
  24677. };
  24678. var bin = {
  24679. toUnsigned: toUnsigned
  24680. };
  24681. var toUnsigned$1 = bin.toUnsigned;
  24682. var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
  24683. _findBox = function findBox(data, path) {
  24684. var results = [],
  24685. i,
  24686. size,
  24687. type,
  24688. end,
  24689. subresults;
  24690. if (!path.length) {
  24691. // short-circuit the search for empty paths
  24692. return null;
  24693. }
  24694. for (i = 0; i < data.byteLength;) {
  24695. size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  24696. type = parseType(data.subarray(i + 4, i + 8));
  24697. end = size > 1 ? i + size : data.byteLength;
  24698. if (type === path[0]) {
  24699. if (path.length === 1) {
  24700. // this is the end of the path and we've found the box we were
  24701. // looking for
  24702. results.push(data.subarray(i + 8, end));
  24703. } else {
  24704. // recursively search for the next box along the path
  24705. subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
  24706. if (subresults.length) {
  24707. results = results.concat(subresults);
  24708. }
  24709. }
  24710. }
  24711. i = end;
  24712. } // we've finished searching all of data
  24713. return results;
  24714. };
  24715. /**
  24716. * Returns the string representation of an ASCII encoded four byte buffer.
  24717. * @param buffer {Uint8Array} a four-byte buffer to translate
  24718. * @return {string} the corresponding string
  24719. */
  24720. parseType = function parseType(buffer) {
  24721. var result = '';
  24722. result += String.fromCharCode(buffer[0]);
  24723. result += String.fromCharCode(buffer[1]);
  24724. result += String.fromCharCode(buffer[2]);
  24725. result += String.fromCharCode(buffer[3]);
  24726. return result;
  24727. };
  24728. /**
  24729. * Parses an MP4 initialization segment and extracts the timescale
  24730. * values for any declared tracks. Timescale values indicate the
  24731. * number of clock ticks per second to assume for time-based values
  24732. * elsewhere in the MP4.
  24733. *
  24734. * To determine the start time of an MP4, you need two pieces of
  24735. * information: the timescale unit and the earliest base media decode
  24736. * time. Multiple timescales can be specified within an MP4 but the
  24737. * base media decode time is always expressed in the timescale from
  24738. * the media header box for the track:
  24739. * ```
  24740. * moov > trak > mdia > mdhd.timescale
  24741. * ```
  24742. * @param init {Uint8Array} the bytes of the init segment
  24743. * @return {object} a hash of track ids to timescale values or null if
  24744. * the init segment is malformed.
  24745. */
  24746. timescale = function timescale(init) {
  24747. var result = {},
  24748. traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
  24749. return traks.reduce(function (result, trak) {
  24750. var tkhd, version, index, id, mdhd;
  24751. tkhd = _findBox(trak, ['tkhd'])[0];
  24752. if (!tkhd) {
  24753. return null;
  24754. }
  24755. version = tkhd[0];
  24756. index = version === 0 ? 12 : 20;
  24757. id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  24758. mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
  24759. if (!mdhd) {
  24760. return null;
  24761. }
  24762. version = mdhd[0];
  24763. index = version === 0 ? 12 : 20;
  24764. result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  24765. return result;
  24766. }, result);
  24767. };
  24768. /**
  24769. * Determine the base media decode start time, in seconds, for an MP4
  24770. * fragment. If multiple fragments are specified, the earliest time is
  24771. * returned.
  24772. *
  24773. * The base media decode time can be parsed from track fragment
  24774. * metadata:
  24775. * ```
  24776. * moof > traf > tfdt.baseMediaDecodeTime
  24777. * ```
  24778. * It requires the timescale value from the mdhd to interpret.
  24779. *
  24780. * @param timescale {object} a hash of track ids to timescale values.
  24781. * @return {number} the earliest base media decode start time for the
  24782. * fragment, in seconds
  24783. */
  24784. startTime = function startTime(timescale, fragment) {
  24785. var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
  24786. trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
  24787. baseTimes = [].concat.apply([], trafs.map(function (traf) {
  24788. return _findBox(traf, ['tfhd']).map(function (tfhd) {
  24789. var id, scale, baseTime; // get the track id from the tfhd
  24790. id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  24791. scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  24792. baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
  24793. var version, result;
  24794. version = tfdt[0];
  24795. result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
  24796. if (version === 1) {
  24797. result *= Math.pow(2, 32);
  24798. result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
  24799. }
  24800. return result;
  24801. })[0];
  24802. baseTime = baseTime || Infinity; // convert base time to seconds
  24803. return baseTime / scale;
  24804. });
  24805. })); // return the minimum
  24806. result = Math.min.apply(null, baseTimes);
  24807. return isFinite(result) ? result : 0;
  24808. };
  24809. /**
  24810. * Find the trackIds of the video tracks in this source.
  24811. * Found by parsing the Handler Reference and Track Header Boxes:
  24812. * moov > trak > mdia > hdlr
  24813. * moov > trak > tkhd
  24814. *
  24815. * @param {Uint8Array} init - The bytes of the init segment for this source
  24816. * @return {Number[]} A list of trackIds
  24817. *
  24818. * @see ISO-BMFF-12/2015, Section 8.4.3
  24819. **/
  24820. getVideoTrackIds = function getVideoTrackIds(init) {
  24821. var traks = _findBox(init, ['moov', 'trak']);
  24822. var videoTrackIds = [];
  24823. traks.forEach(function (trak) {
  24824. var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
  24825. var tkhds = _findBox(trak, ['tkhd']);
  24826. hdlrs.forEach(function (hdlr, index) {
  24827. var handlerType = parseType(hdlr.subarray(8, 12));
  24828. var tkhd = tkhds[index];
  24829. var view;
  24830. var version;
  24831. var trackId;
  24832. if (handlerType === 'vide') {
  24833. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  24834. version = view.getUint8(0);
  24835. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  24836. videoTrackIds.push(trackId);
  24837. }
  24838. });
  24839. });
  24840. return videoTrackIds;
  24841. };
  24842. var probe = {
  24843. findBox: _findBox,
  24844. parseType: parseType,
  24845. timescale: timescale,
  24846. startTime: startTime,
  24847. videoTrackIds: getVideoTrackIds
  24848. };
  24849. /**
  24850. * mux.js
  24851. *
  24852. * Copyright (c) 2014 Brightcove
  24853. * All rights reserved.
  24854. *
  24855. * A lightweight readable stream implemention that handles event dispatching.
  24856. * Objects that inherit from streams should call init in their constructors.
  24857. */
  24858. var Stream = function Stream() {
  24859. this.init = function () {
  24860. var listeners = {};
  24861. /**
  24862. * Add a listener for a specified event type.
  24863. * @param type {string} the event name
  24864. * @param listener {function} the callback to be invoked when an event of
  24865. * the specified type occurs
  24866. */
  24867. this.on = function (type, listener) {
  24868. if (!listeners[type]) {
  24869. listeners[type] = [];
  24870. }
  24871. listeners[type] = listeners[type].concat(listener);
  24872. };
  24873. /**
  24874. * Remove a listener for a specified event type.
  24875. * @param type {string} the event name
  24876. * @param listener {function} a function previously registered for this
  24877. * type of event through `on`
  24878. */
  24879. this.off = function (type, listener) {
  24880. var index;
  24881. if (!listeners[type]) {
  24882. return false;
  24883. }
  24884. index = listeners[type].indexOf(listener);
  24885. listeners[type] = listeners[type].slice();
  24886. listeners[type].splice(index, 1);
  24887. return index > -1;
  24888. };
  24889. /**
  24890. * Trigger an event of the specified type on this stream. Any additional
  24891. * arguments to this function are passed as parameters to event listeners.
  24892. * @param type {string} the event name
  24893. */
  24894. this.trigger = function (type) {
  24895. var callbacks, i, length, args;
  24896. callbacks = listeners[type];
  24897. if (!callbacks) {
  24898. return;
  24899. } // Slicing the arguments on every invocation of this method
  24900. // can add a significant amount of overhead. Avoid the
  24901. // intermediate object creation for the common case of a
  24902. // single callback argument
  24903. if (arguments.length === 2) {
  24904. length = callbacks.length;
  24905. for (i = 0; i < length; ++i) {
  24906. callbacks[i].call(this, arguments[1]);
  24907. }
  24908. } else {
  24909. args = [];
  24910. i = arguments.length;
  24911. for (i = 1; i < arguments.length; ++i) {
  24912. args.push(arguments[i]);
  24913. }
  24914. length = callbacks.length;
  24915. for (i = 0; i < length; ++i) {
  24916. callbacks[i].apply(this, args);
  24917. }
  24918. }
  24919. };
  24920. /**
  24921. * Destroys the stream and cleans up.
  24922. */
  24923. this.dispose = function () {
  24924. listeners = {};
  24925. };
  24926. };
  24927. };
  24928. /**
  24929. * Forwards all `data` events on this stream to the destination stream. The
  24930. * destination stream should provide a method `push` to receive the data
  24931. * events as they arrive.
  24932. * @param destination {stream} the stream that will receive all `data` events
  24933. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  24934. * when the current stream emits a 'done' event
  24935. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  24936. */
  24937. Stream.prototype.pipe = function (destination) {
  24938. this.on('data', function (data) {
  24939. destination.push(data);
  24940. });
  24941. this.on('done', function (flushSource) {
  24942. destination.flush(flushSource);
  24943. });
  24944. return destination;
  24945. }; // Default stream functions that are expected to be overridden to perform
  24946. // actual work. These are provided by the prototype as a sort of no-op
  24947. // implementation so that we don't have to check for their existence in the
  24948. // `pipe` function above.
  24949. Stream.prototype.push = function (data) {
  24950. this.trigger('data', data);
  24951. };
  24952. Stream.prototype.flush = function (flushSource) {
  24953. this.trigger('done', flushSource);
  24954. };
  24955. var stream = Stream; // Convert an array of nal units into an array of frames with each frame being
  24956. // composed of the nal units that make up that frame
  24957. // Also keep track of cummulative data about the frame from the nal units such
  24958. // as the frame duration, starting pts, etc.
  24959. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  24960. var i,
  24961. currentNal,
  24962. currentFrame = [],
  24963. frames = [];
  24964. currentFrame.byteLength = 0;
  24965. for (i = 0; i < nalUnits.length; i++) {
  24966. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  24967. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  24968. // Since the very first nal unit is expected to be an AUD
  24969. // only push to the frames array when currentFrame is not empty
  24970. if (currentFrame.length) {
  24971. currentFrame.duration = currentNal.dts - currentFrame.dts;
  24972. frames.push(currentFrame);
  24973. }
  24974. currentFrame = [currentNal];
  24975. currentFrame.byteLength = currentNal.data.byteLength;
  24976. currentFrame.pts = currentNal.pts;
  24977. currentFrame.dts = currentNal.dts;
  24978. } else {
  24979. // Specifically flag key frames for ease of use later
  24980. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  24981. currentFrame.keyFrame = true;
  24982. }
  24983. currentFrame.duration = currentNal.dts - currentFrame.dts;
  24984. currentFrame.byteLength += currentNal.data.byteLength;
  24985. currentFrame.push(currentNal);
  24986. }
  24987. } // For the last frame, use the duration of the previous frame if we
  24988. // have nothing better to go on
  24989. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  24990. currentFrame.duration = frames[frames.length - 1].duration;
  24991. } // Push the final frame
  24992. frames.push(currentFrame);
  24993. return frames;
  24994. }; // Convert an array of frames into an array of Gop with each Gop being composed
  24995. // of the frames that make up that Gop
  24996. // Also keep track of cummulative data about the Gop from the frames such as the
  24997. // Gop duration, starting pts, etc.
  24998. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  24999. var i,
  25000. currentFrame,
  25001. currentGop = [],
  25002. gops = []; // We must pre-set some of the values on the Gop since we
  25003. // keep running totals of these values
  25004. currentGop.byteLength = 0;
  25005. currentGop.nalCount = 0;
  25006. currentGop.duration = 0;
  25007. currentGop.pts = frames[0].pts;
  25008. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  25009. gops.byteLength = 0;
  25010. gops.nalCount = 0;
  25011. gops.duration = 0;
  25012. gops.pts = frames[0].pts;
  25013. gops.dts = frames[0].dts;
  25014. for (i = 0; i < frames.length; i++) {
  25015. currentFrame = frames[i];
  25016. if (currentFrame.keyFrame) {
  25017. // Since the very first frame is expected to be an keyframe
  25018. // only push to the gops array when currentGop is not empty
  25019. if (currentGop.length) {
  25020. gops.push(currentGop);
  25021. gops.byteLength += currentGop.byteLength;
  25022. gops.nalCount += currentGop.nalCount;
  25023. gops.duration += currentGop.duration;
  25024. }
  25025. currentGop = [currentFrame];
  25026. currentGop.nalCount = currentFrame.length;
  25027. currentGop.byteLength = currentFrame.byteLength;
  25028. currentGop.pts = currentFrame.pts;
  25029. currentGop.dts = currentFrame.dts;
  25030. currentGop.duration = currentFrame.duration;
  25031. } else {
  25032. currentGop.duration += currentFrame.duration;
  25033. currentGop.nalCount += currentFrame.length;
  25034. currentGop.byteLength += currentFrame.byteLength;
  25035. currentGop.push(currentFrame);
  25036. }
  25037. }
  25038. if (gops.length && currentGop.duration <= 0) {
  25039. currentGop.duration = gops[gops.length - 1].duration;
  25040. }
  25041. gops.byteLength += currentGop.byteLength;
  25042. gops.nalCount += currentGop.nalCount;
  25043. gops.duration += currentGop.duration; // push the final Gop
  25044. gops.push(currentGop);
  25045. return gops;
  25046. };
  25047. /*
  25048. * Search for the first keyframe in the GOPs and throw away all frames
  25049. * until that keyframe. Then extend the duration of the pulled keyframe
  25050. * and pull the PTS and DTS of the keyframe so that it covers the time
  25051. * range of the frames that were disposed.
  25052. *
  25053. * @param {Array} gops video GOPs
  25054. * @returns {Array} modified video GOPs
  25055. */
  25056. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  25057. var currentGop;
  25058. if (!gops[0][0].keyFrame && gops.length > 1) {
  25059. // Remove the first GOP
  25060. currentGop = gops.shift();
  25061. gops.byteLength -= currentGop.byteLength;
  25062. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  25063. // first gop to cover the time period of the
  25064. // frames we just removed
  25065. gops[0][0].dts = currentGop.dts;
  25066. gops[0][0].pts = currentGop.pts;
  25067. gops[0][0].duration += currentGop.duration;
  25068. }
  25069. return gops;
  25070. };
  25071. /**
  25072. * Default sample object
  25073. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  25074. */
  25075. var createDefaultSample = function createDefaultSample() {
  25076. return {
  25077. size: 0,
  25078. flags: {
  25079. isLeading: 0,
  25080. dependsOn: 1,
  25081. isDependedOn: 0,
  25082. hasRedundancy: 0,
  25083. degradationPriority: 0,
  25084. isNonSyncSample: 1
  25085. }
  25086. };
  25087. };
  25088. /*
  25089. * Collates information from a video frame into an object for eventual
  25090. * entry into an MP4 sample table.
  25091. *
  25092. * @param {Object} frame the video frame
  25093. * @param {Number} dataOffset the byte offset to position the sample
  25094. * @return {Object} object containing sample table info for a frame
  25095. */
  25096. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  25097. var sample = createDefaultSample();
  25098. sample.dataOffset = dataOffset;
  25099. sample.compositionTimeOffset = frame.pts - frame.dts;
  25100. sample.duration = frame.duration;
  25101. sample.size = 4 * frame.length; // Space for nal unit size
  25102. sample.size += frame.byteLength;
  25103. if (frame.keyFrame) {
  25104. sample.flags.dependsOn = 2;
  25105. sample.flags.isNonSyncSample = 0;
  25106. }
  25107. return sample;
  25108. }; // generate the track's sample table from an array of gops
  25109. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  25110. var h,
  25111. i,
  25112. sample,
  25113. currentGop,
  25114. currentFrame,
  25115. dataOffset = baseDataOffset || 0,
  25116. samples = [];
  25117. for (h = 0; h < gops.length; h++) {
  25118. currentGop = gops[h];
  25119. for (i = 0; i < currentGop.length; i++) {
  25120. currentFrame = currentGop[i];
  25121. sample = sampleForFrame(currentFrame, dataOffset);
  25122. dataOffset += sample.size;
  25123. samples.push(sample);
  25124. }
  25125. }
  25126. return samples;
  25127. }; // generate the track's raw mdat data from an array of gops
  25128. var concatenateNalData = function concatenateNalData(gops) {
  25129. var h,
  25130. i,
  25131. j,
  25132. currentGop,
  25133. currentFrame,
  25134. currentNal,
  25135. dataOffset = 0,
  25136. nalsByteLength = gops.byteLength,
  25137. numberOfNals = gops.nalCount,
  25138. totalByteLength = nalsByteLength + 4 * numberOfNals,
  25139. data = new Uint8Array(totalByteLength),
  25140. view = new DataView(data.buffer); // For each Gop..
  25141. for (h = 0; h < gops.length; h++) {
  25142. currentGop = gops[h]; // For each Frame..
  25143. for (i = 0; i < currentGop.length; i++) {
  25144. currentFrame = currentGop[i]; // For each NAL..
  25145. for (j = 0; j < currentFrame.length; j++) {
  25146. currentNal = currentFrame[j];
  25147. view.setUint32(dataOffset, currentNal.data.byteLength);
  25148. dataOffset += 4;
  25149. data.set(currentNal.data, dataOffset);
  25150. dataOffset += currentNal.data.byteLength;
  25151. }
  25152. }
  25153. }
  25154. return data;
  25155. };
  25156. var frameUtils = {
  25157. groupNalsIntoFrames: groupNalsIntoFrames,
  25158. groupFramesIntoGops: groupFramesIntoGops,
  25159. extendFirstKeyFrame: extendFirstKeyFrame,
  25160. generateSampleTable: generateSampleTable,
  25161. concatenateNalData: concatenateNalData
  25162. };
  25163. var highPrefix = [33, 16, 5, 32, 164, 27];
  25164. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  25165. var zeroFill = function zeroFill(count) {
  25166. var a = [];
  25167. while (count--) {
  25168. a.push(0);
  25169. }
  25170. return a;
  25171. };
  25172. var makeTable = function makeTable(metaTable) {
  25173. return Object.keys(metaTable).reduce(function (obj, key) {
  25174. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  25175. return arr.concat(part);
  25176. }, []));
  25177. return obj;
  25178. }, {});
  25179. }; // Frames-of-silence to use for filling in missing AAC frames
  25180. var coneOfSilence = {
  25181. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  25182. 88200: [highPrefix, [231], zeroFill(170), [56]],
  25183. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  25184. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  25185. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  25186. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  25187. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  25188. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  25189. 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]],
  25190. 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]],
  25191. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  25192. };
  25193. var silence = makeTable(coneOfSilence);
  25194. var ONE_SECOND_IN_TS = 90000,
  25195. // 90kHz clock
  25196. secondsToVideoTs,
  25197. secondsToAudioTs,
  25198. videoTsToSeconds,
  25199. audioTsToSeconds,
  25200. audioTsToVideoTs,
  25201. videoTsToAudioTs;
  25202. secondsToVideoTs = function secondsToVideoTs(seconds) {
  25203. return seconds * ONE_SECOND_IN_TS;
  25204. };
  25205. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  25206. return seconds * sampleRate;
  25207. };
  25208. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  25209. return timestamp / ONE_SECOND_IN_TS;
  25210. };
  25211. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  25212. return timestamp / sampleRate;
  25213. };
  25214. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  25215. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  25216. };
  25217. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  25218. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  25219. };
  25220. var clock = {
  25221. secondsToVideoTs: secondsToVideoTs,
  25222. secondsToAudioTs: secondsToAudioTs,
  25223. videoTsToSeconds: videoTsToSeconds,
  25224. audioTsToSeconds: audioTsToSeconds,
  25225. audioTsToVideoTs: audioTsToVideoTs,
  25226. videoTsToAudioTs: videoTsToAudioTs
  25227. };
  25228. var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
  25229. /**
  25230. * Sum the `byteLength` properties of the data in each AAC frame
  25231. */
  25232. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  25233. var i,
  25234. currentObj,
  25235. sum = 0; // sum the byteLength's all each nal unit in the frame
  25236. for (i = 0; i < array.length; i++) {
  25237. currentObj = array[i];
  25238. sum += currentObj.data.byteLength;
  25239. }
  25240. return sum;
  25241. }; // Possibly pad (prefix) the audio track with silence if appending this track
  25242. // would lead to the introduction of a gap in the audio buffer
  25243. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  25244. var baseMediaDecodeTimeTs,
  25245. frameDuration = 0,
  25246. audioGapDuration = 0,
  25247. audioFillFrameCount = 0,
  25248. audioFillDuration = 0,
  25249. silentFrame,
  25250. i;
  25251. if (!frames.length) {
  25252. return;
  25253. }
  25254. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  25255. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
  25256. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  25257. // insert the shortest possible amount (audio gap or audio to video gap)
  25258. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  25259. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  25260. audioFillDuration = audioFillFrameCount * frameDuration;
  25261. } // don't attempt to fill gaps smaller than a single frame or larger
  25262. // than a half second
  25263. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
  25264. return;
  25265. }
  25266. silentFrame = silence[track.samplerate];
  25267. if (!silentFrame) {
  25268. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  25269. // from the content instead
  25270. silentFrame = frames[0].data;
  25271. }
  25272. for (i = 0; i < audioFillFrameCount; i++) {
  25273. frames.splice(i, 0, {
  25274. data: silentFrame
  25275. });
  25276. }
  25277. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  25278. }; // If the audio segment extends before the earliest allowed dts
  25279. // value, remove AAC frames until starts at or after the earliest
  25280. // allowed DTS so that we don't end up with a negative baseMedia-
  25281. // DecodeTime for the audio track
  25282. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  25283. if (track.minSegmentDts >= earliestAllowedDts) {
  25284. return adtsFrames;
  25285. } // We will need to recalculate the earliest segment Dts
  25286. track.minSegmentDts = Infinity;
  25287. return adtsFrames.filter(function (currentFrame) {
  25288. // If this is an allowed frame, keep it and record it's Dts
  25289. if (currentFrame.dts >= earliestAllowedDts) {
  25290. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  25291. track.minSegmentPts = track.minSegmentDts;
  25292. return true;
  25293. } // Otherwise, discard it
  25294. return false;
  25295. });
  25296. }; // generate the track's raw mdat data from an array of frames
  25297. var generateSampleTable$1 = function generateSampleTable(frames) {
  25298. var i,
  25299. currentFrame,
  25300. samples = [];
  25301. for (i = 0; i < frames.length; i++) {
  25302. currentFrame = frames[i];
  25303. samples.push({
  25304. size: currentFrame.data.byteLength,
  25305. duration: 1024 // For AAC audio, all samples contain 1024 samples
  25306. });
  25307. }
  25308. return samples;
  25309. }; // generate the track's sample table from an array of frames
  25310. var concatenateFrameData = function concatenateFrameData(frames) {
  25311. var i,
  25312. currentFrame,
  25313. dataOffset = 0,
  25314. data = new Uint8Array(sumFrameByteLengths(frames));
  25315. for (i = 0; i < frames.length; i++) {
  25316. currentFrame = frames[i];
  25317. data.set(currentFrame.data, dataOffset);
  25318. dataOffset += currentFrame.data.byteLength;
  25319. }
  25320. return data;
  25321. };
  25322. var audioFrameUtils = {
  25323. prefixWithSilence: prefixWithSilence,
  25324. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  25325. generateSampleTable: generateSampleTable$1,
  25326. concatenateFrameData: concatenateFrameData
  25327. };
  25328. var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
  25329. /**
  25330. * Store information about the start and end of the track and the
  25331. * duration for each frame/sample we process in order to calculate
  25332. * the baseMediaDecodeTime
  25333. */
  25334. var collectDtsInfo = function collectDtsInfo(track, data) {
  25335. if (typeof data.pts === 'number') {
  25336. if (track.timelineStartInfo.pts === undefined) {
  25337. track.timelineStartInfo.pts = data.pts;
  25338. }
  25339. if (track.minSegmentPts === undefined) {
  25340. track.minSegmentPts = data.pts;
  25341. } else {
  25342. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  25343. }
  25344. if (track.maxSegmentPts === undefined) {
  25345. track.maxSegmentPts = data.pts;
  25346. } else {
  25347. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  25348. }
  25349. }
  25350. if (typeof data.dts === 'number') {
  25351. if (track.timelineStartInfo.dts === undefined) {
  25352. track.timelineStartInfo.dts = data.dts;
  25353. }
  25354. if (track.minSegmentDts === undefined) {
  25355. track.minSegmentDts = data.dts;
  25356. } else {
  25357. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  25358. }
  25359. if (track.maxSegmentDts === undefined) {
  25360. track.maxSegmentDts = data.dts;
  25361. } else {
  25362. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  25363. }
  25364. }
  25365. };
  25366. /**
  25367. * Clear values used to calculate the baseMediaDecodeTime between
  25368. * tracks
  25369. */
  25370. var clearDtsInfo = function clearDtsInfo(track) {
  25371. delete track.minSegmentDts;
  25372. delete track.maxSegmentDts;
  25373. delete track.minSegmentPts;
  25374. delete track.maxSegmentPts;
  25375. };
  25376. /**
  25377. * Calculate the track's baseMediaDecodeTime based on the earliest
  25378. * DTS the transmuxer has ever seen and the minimum DTS for the
  25379. * current track
  25380. * @param track {object} track metadata configuration
  25381. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  25382. * in the source; false to adjust the first segment to start at 0.
  25383. */
  25384. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  25385. var baseMediaDecodeTime,
  25386. scale,
  25387. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  25388. if (!keepOriginalTimestamps) {
  25389. minSegmentDts -= track.timelineStartInfo.dts;
  25390. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  25391. // we want the start of the first segment to be placed
  25392. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  25393. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  25394. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  25395. if (track.type === 'audio') {
  25396. // Audio has a different clock equal to the sampling_rate so we need to
  25397. // scale the PTS values into the clock rate of the track
  25398. scale = track.samplerate / ONE_SECOND_IN_TS$2;
  25399. baseMediaDecodeTime *= scale;
  25400. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  25401. }
  25402. return baseMediaDecodeTime;
  25403. };
  25404. var trackDecodeInfo = {
  25405. clearDtsInfo: clearDtsInfo,
  25406. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  25407. collectDtsInfo: collectDtsInfo
  25408. };
  25409. /**
  25410. * mux.js
  25411. *
  25412. * Copyright (c) 2015 Brightcove
  25413. * All rights reserved.
  25414. *
  25415. * Reads in-band caption information from a video elementary
  25416. * stream. Captions must follow the CEA-708 standard for injection
  25417. * into an MPEG-2 transport streams.
  25418. * @see https://en.wikipedia.org/wiki/CEA-708
  25419. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  25420. */
  25421. // Supplemental enhancement information (SEI) NAL units have a
  25422. // payload type field to indicate how they are to be
  25423. // interpreted. CEAS-708 caption content is always transmitted with
  25424. // payload type 0x04.
  25425. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  25426. RBSP_TRAILING_BITS = 128;
  25427. /**
  25428. * Parse a supplemental enhancement information (SEI) NAL unit.
  25429. * Stops parsing once a message of type ITU T T35 has been found.
  25430. *
  25431. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  25432. * @return {object} the parsed SEI payload
  25433. * @see Rec. ITU-T H.264, 7.3.2.3.1
  25434. */
  25435. var parseSei = function parseSei(bytes) {
  25436. var i = 0,
  25437. result = {
  25438. payloadType: -1,
  25439. payloadSize: 0
  25440. },
  25441. payloadType = 0,
  25442. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  25443. while (i < bytes.byteLength) {
  25444. // stop once we have hit the end of the sei_rbsp
  25445. if (bytes[i] === RBSP_TRAILING_BITS) {
  25446. break;
  25447. } // Parse payload type
  25448. while (bytes[i] === 0xFF) {
  25449. payloadType += 255;
  25450. i++;
  25451. }
  25452. payloadType += bytes[i++]; // Parse payload size
  25453. while (bytes[i] === 0xFF) {
  25454. payloadSize += 255;
  25455. i++;
  25456. }
  25457. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  25458. // there can only ever be one caption message in a frame's sei
  25459. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  25460. result.payloadType = payloadType;
  25461. result.payloadSize = payloadSize;
  25462. result.payload = bytes.subarray(i, i + payloadSize);
  25463. break;
  25464. } // skip the payload and parse the next message
  25465. i += payloadSize;
  25466. payloadType = 0;
  25467. payloadSize = 0;
  25468. }
  25469. return result;
  25470. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  25471. var parseUserData = function parseUserData(sei) {
  25472. // itu_t_t35_contry_code must be 181 (United States) for
  25473. // captions
  25474. if (sei.payload[0] !== 181) {
  25475. return null;
  25476. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  25477. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  25478. return null;
  25479. } // the user_identifier should be "GA94" to indicate ATSC1 data
  25480. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  25481. return null;
  25482. } // finally, user_data_type_code should be 0x03 for caption data
  25483. if (sei.payload[7] !== 0x03) {
  25484. return null;
  25485. } // return the user_data_type_structure and strip the trailing
  25486. // marker bits
  25487. return sei.payload.subarray(8, sei.payload.length - 1);
  25488. }; // see CEA-708-D, section 4.4
  25489. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  25490. var results = [],
  25491. i,
  25492. count,
  25493. offset,
  25494. data; // if this is just filler, return immediately
  25495. if (!(userData[0] & 0x40)) {
  25496. return results;
  25497. } // parse out the cc_data_1 and cc_data_2 fields
  25498. count = userData[0] & 0x1f;
  25499. for (i = 0; i < count; i++) {
  25500. offset = i * 3;
  25501. data = {
  25502. type: userData[offset + 2] & 0x03,
  25503. pts: pts
  25504. }; // capture cc data when cc_valid is 1
  25505. if (userData[offset + 2] & 0x04) {
  25506. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  25507. results.push(data);
  25508. }
  25509. }
  25510. return results;
  25511. };
  25512. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  25513. var length = data.byteLength,
  25514. emulationPreventionBytesPositions = [],
  25515. i = 1,
  25516. newLength,
  25517. newData; // Find all `Emulation Prevention Bytes`
  25518. while (i < length - 2) {
  25519. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  25520. emulationPreventionBytesPositions.push(i + 2);
  25521. i += 2;
  25522. } else {
  25523. i++;
  25524. }
  25525. } // If no Emulation Prevention Bytes were found just return the original
  25526. // array
  25527. if (emulationPreventionBytesPositions.length === 0) {
  25528. return data;
  25529. } // Create a new array to hold the NAL unit data
  25530. newLength = length - emulationPreventionBytesPositions.length;
  25531. newData = new Uint8Array(newLength);
  25532. var sourceIndex = 0;
  25533. for (i = 0; i < newLength; sourceIndex++, i++) {
  25534. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  25535. // Skip this byte
  25536. sourceIndex++; // Remove this position index
  25537. emulationPreventionBytesPositions.shift();
  25538. }
  25539. newData[i] = data[sourceIndex];
  25540. }
  25541. return newData;
  25542. }; // exports
  25543. var captionPacketParser = {
  25544. parseSei: parseSei,
  25545. parseUserData: parseUserData,
  25546. parseCaptionPackets: parseCaptionPackets,
  25547. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  25548. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  25549. }; // -----------------
  25550. // Link To Transport
  25551. // -----------------
  25552. var CaptionStream = function CaptionStream() {
  25553. CaptionStream.prototype.init.call(this);
  25554. this.captionPackets_ = [];
  25555. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  25556. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  25557. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  25558. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  25559. ];
  25560. this.reset(); // forward data and done events from CCs to this CaptionStream
  25561. this.ccStreams_.forEach(function (cc) {
  25562. cc.on('data', this.trigger.bind(this, 'data'));
  25563. cc.on('done', this.trigger.bind(this, 'done'));
  25564. }, this);
  25565. };
  25566. CaptionStream.prototype = new stream();
  25567. CaptionStream.prototype.push = function (event) {
  25568. var sei, userData, newCaptionPackets; // only examine SEI NALs
  25569. if (event.nalUnitType !== 'sei_rbsp') {
  25570. return;
  25571. } // parse the sei
  25572. sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
  25573. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  25574. return;
  25575. } // parse out the user data payload
  25576. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  25577. if (!userData) {
  25578. return;
  25579. } // Sometimes, the same segment # will be downloaded twice. To stop the
  25580. // caption data from being processed twice, we track the latest dts we've
  25581. // received and ignore everything with a dts before that. However, since
  25582. // data for a specific dts can be split across packets on either side of
  25583. // a segment boundary, we need to make sure we *don't* ignore the packets
  25584. // from the *next* segment that have dts === this.latestDts_. By constantly
  25585. // tracking the number of packets received with dts === this.latestDts_, we
  25586. // know how many should be ignored once we start receiving duplicates.
  25587. if (event.dts < this.latestDts_) {
  25588. // We've started getting older data, so set the flag.
  25589. this.ignoreNextEqualDts_ = true;
  25590. return;
  25591. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  25592. this.numSameDts_--;
  25593. if (!this.numSameDts_) {
  25594. // We've received the last duplicate packet, time to start processing again
  25595. this.ignoreNextEqualDts_ = false;
  25596. }
  25597. return;
  25598. } // parse out CC data packets and save them for later
  25599. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  25600. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  25601. if (this.latestDts_ !== event.dts) {
  25602. this.numSameDts_ = 0;
  25603. }
  25604. this.numSameDts_++;
  25605. this.latestDts_ = event.dts;
  25606. };
  25607. CaptionStream.prototype.flush = function () {
  25608. // make sure we actually parsed captions before proceeding
  25609. if (!this.captionPackets_.length) {
  25610. this.ccStreams_.forEach(function (cc) {
  25611. cc.flush();
  25612. }, this);
  25613. return;
  25614. } // In Chrome, the Array#sort function is not stable so add a
  25615. // presortIndex that we can use to ensure we get a stable-sort
  25616. this.captionPackets_.forEach(function (elem, idx) {
  25617. elem.presortIndex = idx;
  25618. }); // sort caption byte-pairs based on their PTS values
  25619. this.captionPackets_.sort(function (a, b) {
  25620. if (a.pts === b.pts) {
  25621. return a.presortIndex - b.presortIndex;
  25622. }
  25623. return a.pts - b.pts;
  25624. });
  25625. this.captionPackets_.forEach(function (packet) {
  25626. if (packet.type < 2) {
  25627. // Dispatch packet to the right Cea608Stream
  25628. this.dispatchCea608Packet(packet);
  25629. } // this is where an 'else' would go for a dispatching packets
  25630. // to a theoretical Cea708Stream that handles SERVICEn data
  25631. }, this);
  25632. this.captionPackets_.length = 0;
  25633. this.ccStreams_.forEach(function (cc) {
  25634. cc.flush();
  25635. }, this);
  25636. return;
  25637. };
  25638. CaptionStream.prototype.reset = function () {
  25639. this.latestDts_ = null;
  25640. this.ignoreNextEqualDts_ = false;
  25641. this.numSameDts_ = 0;
  25642. this.activeCea608Channel_ = [null, null];
  25643. this.ccStreams_.forEach(function (ccStream) {
  25644. ccStream.reset();
  25645. });
  25646. };
  25647. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  25648. // NOTE: packet.type is the CEA608 field
  25649. if (this.setsChannel1Active(packet)) {
  25650. this.activeCea608Channel_[packet.type] = 0;
  25651. } else if (this.setsChannel2Active(packet)) {
  25652. this.activeCea608Channel_[packet.type] = 1;
  25653. }
  25654. if (this.activeCea608Channel_[packet.type] === null) {
  25655. // If we haven't received anything to set the active channel, discard the
  25656. // data; we don't want jumbled captions
  25657. return;
  25658. }
  25659. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  25660. };
  25661. CaptionStream.prototype.setsChannel1Active = function (packet) {
  25662. return (packet.ccData & 0x7800) === 0x1000;
  25663. };
  25664. CaptionStream.prototype.setsChannel2Active = function (packet) {
  25665. return (packet.ccData & 0x7800) === 0x1800;
  25666. }; // ----------------------
  25667. // Session to Application
  25668. // ----------------------
  25669. // This hash maps non-ASCII, special, and extended character codes to their
  25670. // proper Unicode equivalent. The first keys that are only a single byte
  25671. // are the non-standard ASCII characters, which simply map the CEA608 byte
  25672. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  25673. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  25674. // can be performed regardless of the field and data channel on which the
  25675. // character code was received.
  25676. var CHARACTER_TRANSLATION = {
  25677. 0x2a: 0xe1,
  25678. // á
  25679. 0x5c: 0xe9,
  25680. // é
  25681. 0x5e: 0xed,
  25682. // í
  25683. 0x5f: 0xf3,
  25684. // ó
  25685. 0x60: 0xfa,
  25686. // ú
  25687. 0x7b: 0xe7,
  25688. // ç
  25689. 0x7c: 0xf7,
  25690. // ÷
  25691. 0x7d: 0xd1,
  25692. // Ñ
  25693. 0x7e: 0xf1,
  25694. // ñ
  25695. 0x7f: 0x2588,
  25696. // █
  25697. 0x0130: 0xae,
  25698. // ®
  25699. 0x0131: 0xb0,
  25700. // °
  25701. 0x0132: 0xbd,
  25702. // ½
  25703. 0x0133: 0xbf,
  25704. // ¿
  25705. 0x0134: 0x2122,
  25706. // ™
  25707. 0x0135: 0xa2,
  25708. // ¢
  25709. 0x0136: 0xa3,
  25710. // £
  25711. 0x0137: 0x266a,
  25712. // ♪
  25713. 0x0138: 0xe0,
  25714. // à
  25715. 0x0139: 0xa0,
  25716. //
  25717. 0x013a: 0xe8,
  25718. // è
  25719. 0x013b: 0xe2,
  25720. // â
  25721. 0x013c: 0xea,
  25722. // ê
  25723. 0x013d: 0xee,
  25724. // î
  25725. 0x013e: 0xf4,
  25726. // ô
  25727. 0x013f: 0xfb,
  25728. // û
  25729. 0x0220: 0xc1,
  25730. // Á
  25731. 0x0221: 0xc9,
  25732. // É
  25733. 0x0222: 0xd3,
  25734. // Ó
  25735. 0x0223: 0xda,
  25736. // Ú
  25737. 0x0224: 0xdc,
  25738. // Ü
  25739. 0x0225: 0xfc,
  25740. // ü
  25741. 0x0226: 0x2018,
  25742. // ‘
  25743. 0x0227: 0xa1,
  25744. // ¡
  25745. 0x0228: 0x2a,
  25746. // *
  25747. 0x0229: 0x27,
  25748. // '
  25749. 0x022a: 0x2014,
  25750. // —
  25751. 0x022b: 0xa9,
  25752. // ©
  25753. 0x022c: 0x2120,
  25754. // ℠
  25755. 0x022d: 0x2022,
  25756. // •
  25757. 0x022e: 0x201c,
  25758. // “
  25759. 0x022f: 0x201d,
  25760. // ”
  25761. 0x0230: 0xc0,
  25762. // À
  25763. 0x0231: 0xc2,
  25764. // Â
  25765. 0x0232: 0xc7,
  25766. // Ç
  25767. 0x0233: 0xc8,
  25768. // È
  25769. 0x0234: 0xca,
  25770. // Ê
  25771. 0x0235: 0xcb,
  25772. // Ë
  25773. 0x0236: 0xeb,
  25774. // ë
  25775. 0x0237: 0xce,
  25776. // Î
  25777. 0x0238: 0xcf,
  25778. // Ï
  25779. 0x0239: 0xef,
  25780. // ï
  25781. 0x023a: 0xd4,
  25782. // Ô
  25783. 0x023b: 0xd9,
  25784. // Ù
  25785. 0x023c: 0xf9,
  25786. // ù
  25787. 0x023d: 0xdb,
  25788. // Û
  25789. 0x023e: 0xab,
  25790. // «
  25791. 0x023f: 0xbb,
  25792. // »
  25793. 0x0320: 0xc3,
  25794. // Ã
  25795. 0x0321: 0xe3,
  25796. // ã
  25797. 0x0322: 0xcd,
  25798. // Í
  25799. 0x0323: 0xcc,
  25800. // Ì
  25801. 0x0324: 0xec,
  25802. // ì
  25803. 0x0325: 0xd2,
  25804. // Ò
  25805. 0x0326: 0xf2,
  25806. // ò
  25807. 0x0327: 0xd5,
  25808. // Õ
  25809. 0x0328: 0xf5,
  25810. // õ
  25811. 0x0329: 0x7b,
  25812. // {
  25813. 0x032a: 0x7d,
  25814. // }
  25815. 0x032b: 0x5c,
  25816. // \
  25817. 0x032c: 0x5e,
  25818. // ^
  25819. 0x032d: 0x5f,
  25820. // _
  25821. 0x032e: 0x7c,
  25822. // |
  25823. 0x032f: 0x7e,
  25824. // ~
  25825. 0x0330: 0xc4,
  25826. // Ä
  25827. 0x0331: 0xe4,
  25828. // ä
  25829. 0x0332: 0xd6,
  25830. // Ö
  25831. 0x0333: 0xf6,
  25832. // ö
  25833. 0x0334: 0xdf,
  25834. // ß
  25835. 0x0335: 0xa5,
  25836. // ¥
  25837. 0x0336: 0xa4,
  25838. // ¤
  25839. 0x0337: 0x2502,
  25840. // │
  25841. 0x0338: 0xc5,
  25842. // Å
  25843. 0x0339: 0xe5,
  25844. // å
  25845. 0x033a: 0xd8,
  25846. // Ø
  25847. 0x033b: 0xf8,
  25848. // ø
  25849. 0x033c: 0x250c,
  25850. // ┌
  25851. 0x033d: 0x2510,
  25852. // ┐
  25853. 0x033e: 0x2514,
  25854. // └
  25855. 0x033f: 0x2518 // ┘
  25856. };
  25857. var getCharFromCode = function getCharFromCode(code) {
  25858. if (code === null) {
  25859. return '';
  25860. }
  25861. code = CHARACTER_TRANSLATION[code] || code;
  25862. return String.fromCharCode(code);
  25863. }; // the index of the last row in a CEA-608 display buffer
  25864. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  25865. // getting it through bit logic.
  25866. 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
  25867. // cells. The "bottom" row is the last element in the outer array.
  25868. var createDisplayBuffer = function createDisplayBuffer() {
  25869. var result = [],
  25870. i = BOTTOM_ROW + 1;
  25871. while (i--) {
  25872. result.push('');
  25873. }
  25874. return result;
  25875. };
  25876. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  25877. Cea608Stream.prototype.init.call(this);
  25878. this.field_ = field || 0;
  25879. this.dataChannel_ = dataChannel || 0;
  25880. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  25881. this.setConstants();
  25882. this.reset();
  25883. this.push = function (packet) {
  25884. var data, swap, char0, char1, text; // remove the parity bits
  25885. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  25886. if (data === this.lastControlCode_) {
  25887. this.lastControlCode_ = null;
  25888. return;
  25889. } // Store control codes
  25890. if ((data & 0xf000) === 0x1000) {
  25891. this.lastControlCode_ = data;
  25892. } else if (data !== this.PADDING_) {
  25893. this.lastControlCode_ = null;
  25894. }
  25895. char0 = data >>> 8;
  25896. char1 = data & 0xff;
  25897. if (data === this.PADDING_) {
  25898. return;
  25899. } else if (data === this.RESUME_CAPTION_LOADING_) {
  25900. this.mode_ = 'popOn';
  25901. } else if (data === this.END_OF_CAPTION_) {
  25902. // If an EOC is received while in paint-on mode, the displayed caption
  25903. // text should be swapped to non-displayed memory as if it was a pop-on
  25904. // caption. Because of that, we should explicitly switch back to pop-on
  25905. // mode
  25906. this.mode_ = 'popOn';
  25907. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  25908. this.flushDisplayed(packet.pts); // flip memory
  25909. swap = this.displayed_;
  25910. this.displayed_ = this.nonDisplayed_;
  25911. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  25912. this.startPts_ = packet.pts;
  25913. } else if (data === this.ROLL_UP_2_ROWS_) {
  25914. this.rollUpRows_ = 2;
  25915. this.setRollUp(packet.pts);
  25916. } else if (data === this.ROLL_UP_3_ROWS_) {
  25917. this.rollUpRows_ = 3;
  25918. this.setRollUp(packet.pts);
  25919. } else if (data === this.ROLL_UP_4_ROWS_) {
  25920. this.rollUpRows_ = 4;
  25921. this.setRollUp(packet.pts);
  25922. } else if (data === this.CARRIAGE_RETURN_) {
  25923. this.clearFormatting(packet.pts);
  25924. this.flushDisplayed(packet.pts);
  25925. this.shiftRowsUp_();
  25926. this.startPts_ = packet.pts;
  25927. } else if (data === this.BACKSPACE_) {
  25928. if (this.mode_ === 'popOn') {
  25929. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  25930. } else {
  25931. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  25932. }
  25933. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  25934. this.flushDisplayed(packet.pts);
  25935. this.displayed_ = createDisplayBuffer();
  25936. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  25937. this.nonDisplayed_ = createDisplayBuffer();
  25938. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  25939. if (this.mode_ !== 'paintOn') {
  25940. // NOTE: This should be removed when proper caption positioning is
  25941. // implemented
  25942. this.flushDisplayed(packet.pts);
  25943. this.displayed_ = createDisplayBuffer();
  25944. }
  25945. this.mode_ = 'paintOn';
  25946. this.startPts_ = packet.pts; // Append special characters to caption text
  25947. } else if (this.isSpecialCharacter(char0, char1)) {
  25948. // Bitmask char0 so that we can apply character transformations
  25949. // regardless of field and data channel.
  25950. // Then byte-shift to the left and OR with char1 so we can pass the
  25951. // entire character code to `getCharFromCode`.
  25952. char0 = (char0 & 0x03) << 8;
  25953. text = getCharFromCode(char0 | char1);
  25954. this[this.mode_](packet.pts, text);
  25955. this.column_++; // Append extended characters to caption text
  25956. } else if (this.isExtCharacter(char0, char1)) {
  25957. // Extended characters always follow their "non-extended" equivalents.
  25958. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  25959. // decoders are supposed to drop the "è", while compliant decoders
  25960. // backspace the "e" and insert "è".
  25961. // Delete the previous character
  25962. if (this.mode_ === 'popOn') {
  25963. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  25964. } else {
  25965. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  25966. } // Bitmask char0 so that we can apply character transformations
  25967. // regardless of field and data channel.
  25968. // Then byte-shift to the left and OR with char1 so we can pass the
  25969. // entire character code to `getCharFromCode`.
  25970. char0 = (char0 & 0x03) << 8;
  25971. text = getCharFromCode(char0 | char1);
  25972. this[this.mode_](packet.pts, text);
  25973. this.column_++; // Process mid-row codes
  25974. } else if (this.isMidRowCode(char0, char1)) {
  25975. // Attributes are not additive, so clear all formatting
  25976. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  25977. // should be replaced with spaces, so add one now
  25978. this[this.mode_](packet.pts, ' ');
  25979. this.column_++;
  25980. if ((char1 & 0xe) === 0xe) {
  25981. this.addFormatting(packet.pts, ['i']);
  25982. }
  25983. if ((char1 & 0x1) === 0x1) {
  25984. this.addFormatting(packet.pts, ['u']);
  25985. } // Detect offset control codes and adjust cursor
  25986. } else if (this.isOffsetControlCode(char0, char1)) {
  25987. // Cursor position is set by indent PAC (see below) in 4-column
  25988. // increments, with an additional offset code of 1-3 to reach any
  25989. // of the 32 columns specified by CEA-608. So all we need to do
  25990. // here is increment the column cursor by the given offset.
  25991. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  25992. } else if (this.isPAC(char0, char1)) {
  25993. // There's no logic for PAC -> row mapping, so we have to just
  25994. // find the row code in an array and use its index :(
  25995. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  25996. if (this.mode_ === 'rollUp') {
  25997. // This implies that the base row is incorrectly set.
  25998. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  25999. // of roll-up rows set.
  26000. if (row - this.rollUpRows_ + 1 < 0) {
  26001. row = this.rollUpRows_ - 1;
  26002. }
  26003. this.setRollUp(packet.pts, row);
  26004. }
  26005. if (row !== this.row_) {
  26006. // formatting is only persistent for current row
  26007. this.clearFormatting(packet.pts);
  26008. this.row_ = row;
  26009. } // All PACs can apply underline, so detect and apply
  26010. // (All odd-numbered second bytes set underline)
  26011. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  26012. this.addFormatting(packet.pts, ['u']);
  26013. }
  26014. if ((data & 0x10) === 0x10) {
  26015. // We've got an indent level code. Each successive even number
  26016. // increments the column cursor by 4, so we can get the desired
  26017. // column position by bit-shifting to the right (to get n/2)
  26018. // and multiplying by 4.
  26019. this.column_ = ((data & 0xe) >> 1) * 4;
  26020. }
  26021. if (this.isColorPAC(char1)) {
  26022. // it's a color code, though we only support white, which
  26023. // can be either normal or italicized. white italics can be
  26024. // either 0x4e or 0x6e depending on the row, so we just
  26025. // bitwise-and with 0xe to see if italics should be turned on
  26026. if ((char1 & 0xe) === 0xe) {
  26027. this.addFormatting(packet.pts, ['i']);
  26028. }
  26029. } // We have a normal character in char0, and possibly one in char1
  26030. } else if (this.isNormalChar(char0)) {
  26031. if (char1 === 0x00) {
  26032. char1 = null;
  26033. }
  26034. text = getCharFromCode(char0);
  26035. text += getCharFromCode(char1);
  26036. this[this.mode_](packet.pts, text);
  26037. this.column_ += text.length;
  26038. } // finish data processing
  26039. };
  26040. };
  26041. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  26042. // display buffer
  26043. Cea608Stream.prototype.flushDisplayed = function (pts) {
  26044. var content = this.displayed_ // remove spaces from the start and end of the string
  26045. .map(function (row) {
  26046. try {
  26047. return row.trim();
  26048. } catch (e) {
  26049. // Ordinarily, this shouldn't happen. However, caption
  26050. // parsing errors should not throw exceptions and
  26051. // break playback.
  26052. // eslint-disable-next-line no-console
  26053. console.error('Skipping malformed caption.');
  26054. return '';
  26055. }
  26056. }) // combine all text rows to display in one cue
  26057. .join('\n') // and remove blank rows from the start and end, but not the middle
  26058. .replace(/^\n+|\n+$/g, '');
  26059. if (content.length) {
  26060. this.trigger('data', {
  26061. startPts: this.startPts_,
  26062. endPts: pts,
  26063. text: content,
  26064. stream: this.name_
  26065. });
  26066. }
  26067. };
  26068. /**
  26069. * Zero out the data, used for startup and on seek
  26070. */
  26071. Cea608Stream.prototype.reset = function () {
  26072. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  26073. // actually display captions. If a caption is shifted to a row
  26074. // with a lower index than this, it is cleared from the display
  26075. // buffer
  26076. this.topRow_ = 0;
  26077. this.startPts_ = 0;
  26078. this.displayed_ = createDisplayBuffer();
  26079. this.nonDisplayed_ = createDisplayBuffer();
  26080. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  26081. this.column_ = 0;
  26082. this.row_ = BOTTOM_ROW;
  26083. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  26084. this.formatting_ = [];
  26085. };
  26086. /**
  26087. * Sets up control code and related constants for this instance
  26088. */
  26089. Cea608Stream.prototype.setConstants = function () {
  26090. // The following attributes have these uses:
  26091. // ext_ : char0 for mid-row codes, and the base for extended
  26092. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  26093. // extended codes)
  26094. // control_: char0 for control codes, except byte-shifted to the
  26095. // left so that we can do this.control_ | CONTROL_CODE
  26096. // offset_: char0 for tab offset codes
  26097. //
  26098. // It's also worth noting that control codes, and _only_ control codes,
  26099. // differ between field 1 and field2. Field 2 control codes are always
  26100. // their field 1 value plus 1. That's why there's the "| field" on the
  26101. // control value.
  26102. if (this.dataChannel_ === 0) {
  26103. this.BASE_ = 0x10;
  26104. this.EXT_ = 0x11;
  26105. this.CONTROL_ = (0x14 | this.field_) << 8;
  26106. this.OFFSET_ = 0x17;
  26107. } else if (this.dataChannel_ === 1) {
  26108. this.BASE_ = 0x18;
  26109. this.EXT_ = 0x19;
  26110. this.CONTROL_ = (0x1c | this.field_) << 8;
  26111. this.OFFSET_ = 0x1f;
  26112. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  26113. // list is not exhaustive. For a more comprehensive listing and semantics see
  26114. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  26115. // Padding
  26116. this.PADDING_ = 0x0000; // Pop-on Mode
  26117. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  26118. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  26119. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  26120. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  26121. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  26122. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  26123. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  26124. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  26125. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  26126. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  26127. };
  26128. /**
  26129. * Detects if the 2-byte packet data is a special character
  26130. *
  26131. * Special characters have a second byte in the range 0x30 to 0x3f,
  26132. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  26133. * data channel 2).
  26134. *
  26135. * @param {Integer} char0 The first byte
  26136. * @param {Integer} char1 The second byte
  26137. * @return {Boolean} Whether the 2 bytes are an special character
  26138. */
  26139. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  26140. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  26141. };
  26142. /**
  26143. * Detects if the 2-byte packet data is an extended character
  26144. *
  26145. * Extended characters have a second byte in the range 0x20 to 0x3f,
  26146. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  26147. * 0x1a or 0x1b (for data channel 2).
  26148. *
  26149. * @param {Integer} char0 The first byte
  26150. * @param {Integer} char1 The second byte
  26151. * @return {Boolean} Whether the 2 bytes are an extended character
  26152. */
  26153. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  26154. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  26155. };
  26156. /**
  26157. * Detects if the 2-byte packet is a mid-row code
  26158. *
  26159. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  26160. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  26161. * channel 2).
  26162. *
  26163. * @param {Integer} char0 The first byte
  26164. * @param {Integer} char1 The second byte
  26165. * @return {Boolean} Whether the 2 bytes are a mid-row code
  26166. */
  26167. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  26168. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  26169. };
  26170. /**
  26171. * Detects if the 2-byte packet is an offset control code
  26172. *
  26173. * Offset control codes have a second byte in the range 0x21 to 0x23,
  26174. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  26175. * data channel 2).
  26176. *
  26177. * @param {Integer} char0 The first byte
  26178. * @param {Integer} char1 The second byte
  26179. * @return {Boolean} Whether the 2 bytes are an offset control code
  26180. */
  26181. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  26182. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  26183. };
  26184. /**
  26185. * Detects if the 2-byte packet is a Preamble Address Code
  26186. *
  26187. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  26188. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  26189. * range 0x40 to 0x7f.
  26190. *
  26191. * @param {Integer} char0 The first byte
  26192. * @param {Integer} char1 The second byte
  26193. * @return {Boolean} Whether the 2 bytes are a PAC
  26194. */
  26195. Cea608Stream.prototype.isPAC = function (char0, char1) {
  26196. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  26197. };
  26198. /**
  26199. * Detects if a packet's second byte is in the range of a PAC color code
  26200. *
  26201. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  26202. * 0x60 to 0x6f.
  26203. *
  26204. * @param {Integer} char1 The second byte
  26205. * @return {Boolean} Whether the byte is a color PAC
  26206. */
  26207. Cea608Stream.prototype.isColorPAC = function (char1) {
  26208. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  26209. };
  26210. /**
  26211. * Detects if a single byte is in the range of a normal character
  26212. *
  26213. * Normal text bytes are in the range 0x20 to 0x7f.
  26214. *
  26215. * @param {Integer} char The byte
  26216. * @return {Boolean} Whether the byte is a normal character
  26217. */
  26218. Cea608Stream.prototype.isNormalChar = function (char) {
  26219. return char >= 0x20 && char <= 0x7f;
  26220. };
  26221. /**
  26222. * Configures roll-up
  26223. *
  26224. * @param {Integer} pts Current PTS
  26225. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  26226. * a new position
  26227. */
  26228. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  26229. // Reset the base row to the bottom row when switching modes
  26230. if (this.mode_ !== 'rollUp') {
  26231. this.row_ = BOTTOM_ROW;
  26232. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  26233. this.flushDisplayed(pts);
  26234. this.nonDisplayed_ = createDisplayBuffer();
  26235. this.displayed_ = createDisplayBuffer();
  26236. }
  26237. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  26238. // move currently displayed captions (up or down) to the new base row
  26239. for (var i = 0; i < this.rollUpRows_; i++) {
  26240. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  26241. this.displayed_[this.row_ - i] = '';
  26242. }
  26243. }
  26244. if (newBaseRow === undefined) {
  26245. newBaseRow = this.row_;
  26246. }
  26247. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  26248. }; // Adds the opening HTML tag for the passed character to the caption text,
  26249. // and keeps track of it for later closing
  26250. Cea608Stream.prototype.addFormatting = function (pts, format) {
  26251. this.formatting_ = this.formatting_.concat(format);
  26252. var text = format.reduce(function (text, format) {
  26253. return text + '<' + format + '>';
  26254. }, '');
  26255. this[this.mode_](pts, text);
  26256. }; // Adds HTML closing tags for current formatting to caption text and
  26257. // clears remembered formatting
  26258. Cea608Stream.prototype.clearFormatting = function (pts) {
  26259. if (!this.formatting_.length) {
  26260. return;
  26261. }
  26262. var text = this.formatting_.reverse().reduce(function (text, format) {
  26263. return text + '</' + format + '>';
  26264. }, '');
  26265. this.formatting_ = [];
  26266. this[this.mode_](pts, text);
  26267. }; // Mode Implementations
  26268. Cea608Stream.prototype.popOn = function (pts, text) {
  26269. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  26270. baseRow += text;
  26271. this.nonDisplayed_[this.row_] = baseRow;
  26272. };
  26273. Cea608Stream.prototype.rollUp = function (pts, text) {
  26274. var baseRow = this.displayed_[this.row_];
  26275. baseRow += text;
  26276. this.displayed_[this.row_] = baseRow;
  26277. };
  26278. Cea608Stream.prototype.shiftRowsUp_ = function () {
  26279. var i; // clear out inactive rows
  26280. for (i = 0; i < this.topRow_; i++) {
  26281. this.displayed_[i] = '';
  26282. }
  26283. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  26284. this.displayed_[i] = '';
  26285. } // shift displayed rows up
  26286. for (i = this.topRow_; i < this.row_; i++) {
  26287. this.displayed_[i] = this.displayed_[i + 1];
  26288. } // clear out the bottom row
  26289. this.displayed_[this.row_] = '';
  26290. };
  26291. Cea608Stream.prototype.paintOn = function (pts, text) {
  26292. var baseRow = this.displayed_[this.row_];
  26293. baseRow += text;
  26294. this.displayed_[this.row_] = baseRow;
  26295. }; // exports
  26296. var captionStream = {
  26297. CaptionStream: CaptionStream,
  26298. Cea608Stream: Cea608Stream
  26299. };
  26300. var streamTypes = {
  26301. H264_STREAM_TYPE: 0x1B,
  26302. ADTS_STREAM_TYPE: 0x0F,
  26303. METADATA_STREAM_TYPE: 0x15
  26304. };
  26305. var MAX_TS = 8589934592;
  26306. var RO_THRESH = 4294967296;
  26307. var handleRollover = function handleRollover(value, reference) {
  26308. var direction = 1;
  26309. if (value > reference) {
  26310. // If the current timestamp value is greater than our reference timestamp and we detect a
  26311. // timestamp rollover, this means the roll over is happening in the opposite direction.
  26312. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  26313. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  26314. // rollover point. In loading this segment, the timestamp values will be very large,
  26315. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  26316. // the time stamp to be `value - 2^33`.
  26317. direction = -1;
  26318. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  26319. // cause an incorrect adjustment.
  26320. while (Math.abs(reference - value) > RO_THRESH) {
  26321. value += direction * MAX_TS;
  26322. }
  26323. return value;
  26324. };
  26325. var TimestampRolloverStream = function TimestampRolloverStream(type) {
  26326. var lastDTS, referenceDTS;
  26327. TimestampRolloverStream.prototype.init.call(this);
  26328. this.type_ = type;
  26329. this.push = function (data) {
  26330. if (data.type !== this.type_) {
  26331. return;
  26332. }
  26333. if (referenceDTS === undefined) {
  26334. referenceDTS = data.dts;
  26335. }
  26336. data.dts = handleRollover(data.dts, referenceDTS);
  26337. data.pts = handleRollover(data.pts, referenceDTS);
  26338. lastDTS = data.dts;
  26339. this.trigger('data', data);
  26340. };
  26341. this.flush = function () {
  26342. referenceDTS = lastDTS;
  26343. this.trigger('done');
  26344. };
  26345. this.discontinuity = function () {
  26346. referenceDTS = void 0;
  26347. lastDTS = void 0;
  26348. };
  26349. };
  26350. TimestampRolloverStream.prototype = new stream();
  26351. var timestampRolloverStream = {
  26352. TimestampRolloverStream: TimestampRolloverStream,
  26353. handleRollover: handleRollover
  26354. };
  26355. var percentEncode = function percentEncode(bytes, start, end) {
  26356. var i,
  26357. result = '';
  26358. for (i = start; i < end; i++) {
  26359. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  26360. }
  26361. return result;
  26362. },
  26363. // return the string representation of the specified byte range,
  26364. // interpreted as UTf-8.
  26365. parseUtf8 = function parseUtf8(bytes, start, end) {
  26366. return decodeURIComponent(percentEncode(bytes, start, end));
  26367. },
  26368. // return the string representation of the specified byte range,
  26369. // interpreted as ISO-8859-1.
  26370. parseIso88591 = function parseIso88591(bytes, start, end) {
  26371. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  26372. },
  26373. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  26374. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  26375. },
  26376. tagParsers = {
  26377. TXXX: function TXXX(tag) {
  26378. var i;
  26379. if (tag.data[0] !== 3) {
  26380. // ignore frames with unrecognized character encodings
  26381. return;
  26382. }
  26383. for (i = 1; i < tag.data.length; i++) {
  26384. if (tag.data[i] === 0) {
  26385. // parse the text fields
  26386. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  26387. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  26388. break;
  26389. }
  26390. }
  26391. tag.data = tag.value;
  26392. },
  26393. WXXX: function WXXX(tag) {
  26394. var i;
  26395. if (tag.data[0] !== 3) {
  26396. // ignore frames with unrecognized character encodings
  26397. return;
  26398. }
  26399. for (i = 1; i < tag.data.length; i++) {
  26400. if (tag.data[i] === 0) {
  26401. // parse the description and URL fields
  26402. tag.description = parseUtf8(tag.data, 1, i);
  26403. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  26404. break;
  26405. }
  26406. }
  26407. },
  26408. PRIV: function PRIV(tag) {
  26409. var i;
  26410. for (i = 0; i < tag.data.length; i++) {
  26411. if (tag.data[i] === 0) {
  26412. // parse the description and URL fields
  26413. tag.owner = parseIso88591(tag.data, 0, i);
  26414. break;
  26415. }
  26416. }
  26417. tag.privateData = tag.data.subarray(i + 1);
  26418. tag.data = tag.privateData;
  26419. }
  26420. },
  26421. _MetadataStream;
  26422. _MetadataStream = function MetadataStream(options) {
  26423. var settings = {
  26424. debug: !!(options && options.debug),
  26425. // the bytes of the program-level descriptor field in MP2T
  26426. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  26427. // program element descriptors"
  26428. descriptor: options && options.descriptor
  26429. },
  26430. // the total size in bytes of the ID3 tag being parsed
  26431. tagSize = 0,
  26432. // tag data that is not complete enough to be parsed
  26433. buffer = [],
  26434. // the total number of bytes currently in the buffer
  26435. bufferSize = 0,
  26436. i;
  26437. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  26438. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  26439. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  26440. if (settings.descriptor) {
  26441. for (i = 0; i < settings.descriptor.length; i++) {
  26442. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  26443. }
  26444. }
  26445. this.push = function (chunk) {
  26446. var tag, frameStart, frameSize, frame, i, frameHeader;
  26447. if (chunk.type !== 'timed-metadata') {
  26448. return;
  26449. } // if data_alignment_indicator is set in the PES header,
  26450. // we must have the start of a new ID3 tag. Assume anything
  26451. // remaining in the buffer was malformed and throw it out
  26452. if (chunk.dataAlignmentIndicator) {
  26453. bufferSize = 0;
  26454. buffer.length = 0;
  26455. } // ignore events that don't look like ID3 data
  26456. 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))) {
  26457. if (settings.debug) {
  26458. // eslint-disable-next-line no-console
  26459. console.log('Skipping unrecognized metadata packet');
  26460. }
  26461. return;
  26462. } // add this chunk to the data we've collected so far
  26463. buffer.push(chunk);
  26464. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  26465. if (buffer.length === 1) {
  26466. // the frame size is transmitted as a 28-bit integer in the
  26467. // last four bytes of the ID3 header.
  26468. // The most significant bit of each byte is dropped and the
  26469. // results concatenated to recover the actual value.
  26470. tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  26471. // convenient for our comparisons to include it
  26472. tagSize += 10;
  26473. } // if the entire frame has not arrived, wait for more data
  26474. if (bufferSize < tagSize) {
  26475. return;
  26476. } // collect the entire frame so it can be parsed
  26477. tag = {
  26478. data: new Uint8Array(tagSize),
  26479. frames: [],
  26480. pts: buffer[0].pts,
  26481. dts: buffer[0].dts
  26482. };
  26483. for (i = 0; i < tagSize;) {
  26484. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  26485. i += buffer[0].data.byteLength;
  26486. bufferSize -= buffer[0].data.byteLength;
  26487. buffer.shift();
  26488. } // find the start of the first frame and the end of the tag
  26489. frameStart = 10;
  26490. if (tag.data[5] & 0x40) {
  26491. // advance the frame start past the extended header
  26492. frameStart += 4; // header size field
  26493. frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  26494. tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
  26495. } // parse one or more ID3 frames
  26496. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  26497. do {
  26498. // determine the number of bytes in this frame
  26499. frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  26500. if (frameSize < 1) {
  26501. // eslint-disable-next-line no-console
  26502. return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
  26503. }
  26504. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  26505. frame = {
  26506. id: frameHeader,
  26507. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  26508. };
  26509. frame.key = frame.id;
  26510. if (tagParsers[frame.id]) {
  26511. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  26512. // time for raw AAC data
  26513. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  26514. var d = frame.data,
  26515. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  26516. size *= 4;
  26517. size += d[7] & 0x03;
  26518. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  26519. // on the value of this frame
  26520. // we couldn't have known the appropriate pts and dts before
  26521. // parsing this ID3 tag so set those values now
  26522. if (tag.pts === undefined && tag.dts === undefined) {
  26523. tag.pts = frame.timeStamp;
  26524. tag.dts = frame.timeStamp;
  26525. }
  26526. this.trigger('timestamp', frame);
  26527. }
  26528. }
  26529. tag.frames.push(frame);
  26530. frameStart += 10; // advance past the frame header
  26531. frameStart += frameSize; // advance past the frame body
  26532. } while (frameStart < tagSize);
  26533. this.trigger('data', tag);
  26534. };
  26535. };
  26536. _MetadataStream.prototype = new stream();
  26537. var metadataStream = _MetadataStream;
  26538. var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
  26539. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  26540. var MP2T_PACKET_LENGTH = 188,
  26541. // bytes
  26542. SYNC_BYTE = 0x47;
  26543. /**
  26544. * Splits an incoming stream of binary data into MPEG-2 Transport
  26545. * Stream packets.
  26546. */
  26547. _TransportPacketStream = function TransportPacketStream() {
  26548. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  26549. bytesInBuffer = 0;
  26550. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  26551. /**
  26552. * Split a stream of data into M2TS packets
  26553. **/
  26554. this.push = function (bytes) {
  26555. var startIndex = 0,
  26556. endIndex = MP2T_PACKET_LENGTH,
  26557. everything; // If there are bytes remaining from the last segment, prepend them to the
  26558. // bytes that were pushed in
  26559. if (bytesInBuffer) {
  26560. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  26561. everything.set(buffer.subarray(0, bytesInBuffer));
  26562. everything.set(bytes, bytesInBuffer);
  26563. bytesInBuffer = 0;
  26564. } else {
  26565. everything = bytes;
  26566. } // While we have enough data for a packet
  26567. while (endIndex < everything.byteLength) {
  26568. // Look for a pair of start and end sync bytes in the data..
  26569. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  26570. // We found a packet so emit it and jump one whole packet forward in
  26571. // the stream
  26572. this.trigger('data', everything.subarray(startIndex, endIndex));
  26573. startIndex += MP2T_PACKET_LENGTH;
  26574. endIndex += MP2T_PACKET_LENGTH;
  26575. continue;
  26576. } // If we get here, we have somehow become de-synchronized and we need to step
  26577. // forward one byte at a time until we find a pair of sync bytes that denote
  26578. // a packet
  26579. startIndex++;
  26580. endIndex++;
  26581. } // If there was some data left over at the end of the segment that couldn't
  26582. // possibly be a whole packet, keep it because it might be the start of a packet
  26583. // that continues in the next segment
  26584. if (startIndex < everything.byteLength) {
  26585. buffer.set(everything.subarray(startIndex), 0);
  26586. bytesInBuffer = everything.byteLength - startIndex;
  26587. }
  26588. };
  26589. /**
  26590. * Passes identified M2TS packets to the TransportParseStream to be parsed
  26591. **/
  26592. this.flush = function () {
  26593. // If the buffer contains a whole packet when we are being flushed, emit it
  26594. // and empty the buffer. Otherwise hold onto the data because it may be
  26595. // important for decoding the next segment
  26596. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  26597. this.trigger('data', buffer);
  26598. bytesInBuffer = 0;
  26599. }
  26600. this.trigger('done');
  26601. };
  26602. };
  26603. _TransportPacketStream.prototype = new stream();
  26604. /**
  26605. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  26606. * forms of the individual transport stream packets.
  26607. */
  26608. _TransportParseStream = function TransportParseStream() {
  26609. var parsePsi, parsePat, parsePmt, self;
  26610. _TransportParseStream.prototype.init.call(this);
  26611. self = this;
  26612. this.packetsWaitingForPmt = [];
  26613. this.programMapTable = undefined;
  26614. parsePsi = function parsePsi(payload, psi) {
  26615. var offset = 0; // PSI packets may be split into multiple sections and those
  26616. // sections may be split into multiple packets. If a PSI
  26617. // section starts in this packet, the payload_unit_start_indicator
  26618. // will be true and the first byte of the payload will indicate
  26619. // the offset from the current position to the start of the
  26620. // section.
  26621. if (psi.payloadUnitStartIndicator) {
  26622. offset += payload[offset] + 1;
  26623. }
  26624. if (psi.type === 'pat') {
  26625. parsePat(payload.subarray(offset), psi);
  26626. } else {
  26627. parsePmt(payload.subarray(offset), psi);
  26628. }
  26629. };
  26630. parsePat = function parsePat(payload, pat) {
  26631. pat.section_number = payload[7]; // eslint-disable-line camelcase
  26632. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  26633. // skip the PSI header and parse the first PMT entry
  26634. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  26635. pat.pmtPid = self.pmtPid;
  26636. };
  26637. /**
  26638. * Parse out the relevant fields of a Program Map Table (PMT).
  26639. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  26640. * packet. The first byte in this array should be the table_id
  26641. * field.
  26642. * @param pmt {object} the object that should be decorated with
  26643. * fields parsed from the PMT.
  26644. */
  26645. parsePmt = function parsePmt(payload, pmt) {
  26646. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  26647. // take effect. We don't believe this should ever be the case
  26648. // for HLS but we'll ignore "forward" PMT declarations if we see
  26649. // them. Future PMT declarations have the current_next_indicator
  26650. // set to zero.
  26651. if (!(payload[5] & 0x01)) {
  26652. return;
  26653. } // overwrite any existing program map table
  26654. self.programMapTable = {
  26655. video: null,
  26656. audio: null,
  26657. 'timed-metadata': {}
  26658. }; // the mapping table ends at the end of the current section
  26659. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  26660. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  26661. // long the program info descriptors are
  26662. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  26663. offset = 12 + programInfoLength;
  26664. while (offset < tableEnd) {
  26665. var streamType = payload[offset];
  26666. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  26667. // TODO: should this be done for metadata too? for now maintain behavior of
  26668. // multiple metadata streams
  26669. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  26670. self.programMapTable.video = pid;
  26671. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  26672. self.programMapTable.audio = pid;
  26673. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  26674. // map pid to stream type for metadata streams
  26675. self.programMapTable['timed-metadata'][pid] = streamType;
  26676. } // move to the next table entry
  26677. // skip past the elementary stream descriptors, if present
  26678. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  26679. } // record the map on the packet as well
  26680. pmt.programMapTable = self.programMapTable;
  26681. };
  26682. /**
  26683. * Deliver a new MP2T packet to the next stream in the pipeline.
  26684. */
  26685. this.push = function (packet) {
  26686. var result = {},
  26687. offset = 4;
  26688. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  26689. result.pid = packet[1] & 0x1f;
  26690. result.pid <<= 8;
  26691. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  26692. // fifth byte of the TS packet header. The adaptation field is
  26693. // used to add stuffing to PES packets that don't fill a complete
  26694. // TS packet, and to specify some forms of timing and control data
  26695. // that we do not currently use.
  26696. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  26697. offset += packet[offset] + 1;
  26698. } // parse the rest of the packet based on the type
  26699. if (result.pid === 0) {
  26700. result.type = 'pat';
  26701. parsePsi(packet.subarray(offset), result);
  26702. this.trigger('data', result);
  26703. } else if (result.pid === this.pmtPid) {
  26704. result.type = 'pmt';
  26705. parsePsi(packet.subarray(offset), result);
  26706. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  26707. while (this.packetsWaitingForPmt.length) {
  26708. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  26709. }
  26710. } else if (this.programMapTable === undefined) {
  26711. // When we have not seen a PMT yet, defer further processing of
  26712. // PES packets until one has been parsed
  26713. this.packetsWaitingForPmt.push([packet, offset, result]);
  26714. } else {
  26715. this.processPes_(packet, offset, result);
  26716. }
  26717. };
  26718. this.processPes_ = function (packet, offset, result) {
  26719. // set the appropriate stream type
  26720. if (result.pid === this.programMapTable.video) {
  26721. result.streamType = streamTypes.H264_STREAM_TYPE;
  26722. } else if (result.pid === this.programMapTable.audio) {
  26723. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  26724. } else {
  26725. // if not video or audio, it is timed-metadata or unknown
  26726. // if unknown, streamType will be undefined
  26727. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  26728. }
  26729. result.type = 'pes';
  26730. result.data = packet.subarray(offset);
  26731. this.trigger('data', result);
  26732. };
  26733. };
  26734. _TransportParseStream.prototype = new stream();
  26735. _TransportParseStream.STREAM_TYPES = {
  26736. h264: 0x1b,
  26737. adts: 0x0f
  26738. };
  26739. /**
  26740. * Reconsistutes program elementary stream (PES) packets from parsed
  26741. * transport stream packets. That is, if you pipe an
  26742. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  26743. * events will be events which capture the bytes for individual PES
  26744. * packets plus relevant metadata that has been extracted from the
  26745. * container.
  26746. */
  26747. _ElementaryStream = function ElementaryStream() {
  26748. var self = this,
  26749. // PES packet fragments
  26750. video = {
  26751. data: [],
  26752. size: 0
  26753. },
  26754. audio = {
  26755. data: [],
  26756. size: 0
  26757. },
  26758. timedMetadata = {
  26759. data: [],
  26760. size: 0
  26761. },
  26762. parsePes = function parsePes(payload, pes) {
  26763. var ptsDtsFlags; // get the packet length, this will be 0 for video
  26764. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  26765. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  26766. // and a DTS value. Determine what combination of values is
  26767. // available to work with.
  26768. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  26769. // performs all bitwise operations on 32-bit integers but javascript
  26770. // supports a much greater range (52-bits) of integer using standard
  26771. // mathematical operations.
  26772. // We construct a 31-bit value using bitwise operators over the 31
  26773. // most significant bits and then multiply by 4 (equal to a left-shift
  26774. // of 2) before we add the final 2 least significant bits of the
  26775. // timestamp (equal to an OR.)
  26776. if (ptsDtsFlags & 0xC0) {
  26777. // the PTS and DTS are not written out directly. For information
  26778. // on how they are encoded, see
  26779. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  26780. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  26781. pes.pts *= 4; // Left shift by 2
  26782. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  26783. pes.dts = pes.pts;
  26784. if (ptsDtsFlags & 0x40) {
  26785. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  26786. pes.dts *= 4; // Left shift by 2
  26787. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  26788. }
  26789. } // the data section starts immediately after the PES header.
  26790. // pes_header_data_length specifies the number of header bytes
  26791. // that follow the last byte of the field.
  26792. pes.data = payload.subarray(9 + payload[8]);
  26793. },
  26794. /**
  26795. * Pass completely parsed PES packets to the next stream in the pipeline
  26796. **/
  26797. flushStream = function flushStream(stream$$1, type, forceFlush) {
  26798. var packetData = new Uint8Array(stream$$1.size),
  26799. event = {
  26800. type: type
  26801. },
  26802. i = 0,
  26803. offset = 0,
  26804. packetFlushable = false,
  26805. fragment; // do nothing if there is not enough buffered data for a complete
  26806. // PES header
  26807. if (!stream$$1.data.length || stream$$1.size < 9) {
  26808. return;
  26809. }
  26810. event.trackId = stream$$1.data[0].pid; // reassemble the packet
  26811. for (i = 0; i < stream$$1.data.length; i++) {
  26812. fragment = stream$$1.data[i];
  26813. packetData.set(fragment.data, offset);
  26814. offset += fragment.data.byteLength;
  26815. } // parse assembled packet's PES header
  26816. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  26817. // check that there is enough stream data to fill the packet
  26818. packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
  26819. if (forceFlush || packetFlushable) {
  26820. stream$$1.size = 0;
  26821. stream$$1.data.length = 0;
  26822. } // only emit packets that are complete. this is to avoid assembling
  26823. // incomplete PES packets due to poor segmentation
  26824. if (packetFlushable) {
  26825. self.trigger('data', event);
  26826. }
  26827. };
  26828. _ElementaryStream.prototype.init.call(this);
  26829. /**
  26830. * Identifies M2TS packet types and parses PES packets using metadata
  26831. * parsed from the PMT
  26832. **/
  26833. this.push = function (data) {
  26834. ({
  26835. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  26836. // have any meaningful metadata
  26837. },
  26838. pes: function pes() {
  26839. var stream$$1, streamType;
  26840. switch (data.streamType) {
  26841. case streamTypes.H264_STREAM_TYPE:
  26842. case streamTypes.H264_STREAM_TYPE:
  26843. stream$$1 = video;
  26844. streamType = 'video';
  26845. break;
  26846. case streamTypes.ADTS_STREAM_TYPE:
  26847. stream$$1 = audio;
  26848. streamType = 'audio';
  26849. break;
  26850. case streamTypes.METADATA_STREAM_TYPE:
  26851. stream$$1 = timedMetadata;
  26852. streamType = 'timed-metadata';
  26853. break;
  26854. default:
  26855. // ignore unknown stream types
  26856. return;
  26857. } // if a new packet is starting, we can flush the completed
  26858. // packet
  26859. if (data.payloadUnitStartIndicator) {
  26860. flushStream(stream$$1, streamType, true);
  26861. } // buffer this fragment until we are sure we've received the
  26862. // complete payload
  26863. stream$$1.data.push(data);
  26864. stream$$1.size += data.data.byteLength;
  26865. },
  26866. pmt: function pmt() {
  26867. var event = {
  26868. type: 'metadata',
  26869. tracks: []
  26870. },
  26871. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  26872. if (programMapTable.video !== null) {
  26873. event.tracks.push({
  26874. timelineStartInfo: {
  26875. baseMediaDecodeTime: 0
  26876. },
  26877. id: +programMapTable.video,
  26878. codec: 'avc',
  26879. type: 'video'
  26880. });
  26881. }
  26882. if (programMapTable.audio !== null) {
  26883. event.tracks.push({
  26884. timelineStartInfo: {
  26885. baseMediaDecodeTime: 0
  26886. },
  26887. id: +programMapTable.audio,
  26888. codec: 'adts',
  26889. type: 'audio'
  26890. });
  26891. }
  26892. self.trigger('data', event);
  26893. }
  26894. })[data.type]();
  26895. };
  26896. /**
  26897. * Flush any remaining input. Video PES packets may be of variable
  26898. * length. Normally, the start of a new video packet can trigger the
  26899. * finalization of the previous packet. That is not possible if no
  26900. * more video is forthcoming, however. In that case, some other
  26901. * mechanism (like the end of the file) has to be employed. When it is
  26902. * clear that no additional data is forthcoming, calling this method
  26903. * will flush the buffered packets.
  26904. */
  26905. this.flush = function () {
  26906. // !!THIS ORDER IS IMPORTANT!!
  26907. // video first then audio
  26908. flushStream(video, 'video');
  26909. flushStream(audio, 'audio');
  26910. flushStream(timedMetadata, 'timed-metadata');
  26911. this.trigger('done');
  26912. };
  26913. };
  26914. _ElementaryStream.prototype = new stream();
  26915. var m2ts = {
  26916. PAT_PID: 0x0000,
  26917. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  26918. TransportPacketStream: _TransportPacketStream,
  26919. TransportParseStream: _TransportParseStream,
  26920. ElementaryStream: _ElementaryStream,
  26921. TimestampRolloverStream: TimestampRolloverStream$1,
  26922. CaptionStream: captionStream.CaptionStream,
  26923. Cea608Stream: captionStream.Cea608Stream,
  26924. MetadataStream: metadataStream
  26925. };
  26926. for (var type in streamTypes) {
  26927. if (streamTypes.hasOwnProperty(type)) {
  26928. m2ts[type] = streamTypes[type];
  26929. }
  26930. }
  26931. var m2ts_1 = m2ts;
  26932. var _AdtsStream;
  26933. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  26934. /*
  26935. * Accepts a ElementaryStream and emits data events with parsed
  26936. * AAC Audio Frames of the individual packets. Input audio in ADTS
  26937. * format is unpacked and re-emitted as AAC frames.
  26938. *
  26939. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  26940. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  26941. */
  26942. _AdtsStream = function AdtsStream() {
  26943. var buffer;
  26944. _AdtsStream.prototype.init.call(this);
  26945. this.push = function (packet) {
  26946. var i = 0,
  26947. frameNum = 0,
  26948. frameLength,
  26949. protectionSkipBytes,
  26950. frameEnd,
  26951. oldBuffer,
  26952. sampleCount,
  26953. adtsFrameDuration;
  26954. if (packet.type !== 'audio') {
  26955. // ignore non-audio data
  26956. return;
  26957. } // Prepend any data in the buffer to the input data so that we can parse
  26958. // aac frames the cross a PES packet boundary
  26959. if (buffer) {
  26960. oldBuffer = buffer;
  26961. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  26962. buffer.set(oldBuffer);
  26963. buffer.set(packet.data, oldBuffer.byteLength);
  26964. } else {
  26965. buffer = packet.data;
  26966. } // unpack any ADTS frames which have been fully received
  26967. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  26968. while (i + 5 < buffer.length) {
  26969. // Loook for the start of an ADTS header..
  26970. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  26971. // If a valid header was not found, jump one forward and attempt to
  26972. // find a valid ADTS header starting at the next byte
  26973. i++;
  26974. continue;
  26975. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  26976. // end of the ADTS header
  26977. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  26978. // end of the sync sequence
  26979. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  26980. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  26981. adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
  26982. frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
  26983. // and wait for more data
  26984. if (buffer.byteLength < frameEnd) {
  26985. return;
  26986. } // Otherwise, deliver the complete AAC frame
  26987. this.trigger('data', {
  26988. pts: packet.pts + frameNum * adtsFrameDuration,
  26989. dts: packet.dts + frameNum * adtsFrameDuration,
  26990. sampleCount: sampleCount,
  26991. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  26992. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  26993. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  26994. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  26995. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  26996. samplesize: 16,
  26997. data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
  26998. }); // If the buffer is empty, clear it and return
  26999. if (buffer.byteLength === frameEnd) {
  27000. buffer = undefined;
  27001. return;
  27002. }
  27003. frameNum++; // Remove the finished frame from the buffer and start the process again
  27004. buffer = buffer.subarray(frameEnd);
  27005. }
  27006. };
  27007. this.flush = function () {
  27008. this.trigger('done');
  27009. };
  27010. };
  27011. _AdtsStream.prototype = new stream();
  27012. var adts = _AdtsStream;
  27013. var ExpGolomb;
  27014. /**
  27015. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  27016. * scheme used by h264.
  27017. */
  27018. ExpGolomb = function ExpGolomb(workingData) {
  27019. var // the number of bytes left to examine in workingData
  27020. workingBytesAvailable = workingData.byteLength,
  27021. // the current word being examined
  27022. workingWord = 0,
  27023. // :uint
  27024. // the number of bits left to examine in the current word
  27025. workingBitsAvailable = 0; // :uint;
  27026. // ():uint
  27027. this.length = function () {
  27028. return 8 * workingBytesAvailable;
  27029. }; // ():uint
  27030. this.bitsAvailable = function () {
  27031. return 8 * workingBytesAvailable + workingBitsAvailable;
  27032. }; // ():void
  27033. this.loadWord = function () {
  27034. var position = workingData.byteLength - workingBytesAvailable,
  27035. workingBytes = new Uint8Array(4),
  27036. availableBytes = Math.min(4, workingBytesAvailable);
  27037. if (availableBytes === 0) {
  27038. throw new Error('no bytes available');
  27039. }
  27040. workingBytes.set(workingData.subarray(position, position + availableBytes));
  27041. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  27042. workingBitsAvailable = availableBytes * 8;
  27043. workingBytesAvailable -= availableBytes;
  27044. }; // (count:int):void
  27045. this.skipBits = function (count) {
  27046. var skipBytes; // :int
  27047. if (workingBitsAvailable > count) {
  27048. workingWord <<= count;
  27049. workingBitsAvailable -= count;
  27050. } else {
  27051. count -= workingBitsAvailable;
  27052. skipBytes = Math.floor(count / 8);
  27053. count -= skipBytes * 8;
  27054. workingBytesAvailable -= skipBytes;
  27055. this.loadWord();
  27056. workingWord <<= count;
  27057. workingBitsAvailable -= count;
  27058. }
  27059. }; // (size:int):uint
  27060. this.readBits = function (size) {
  27061. var bits = Math.min(workingBitsAvailable, size),
  27062. // :uint
  27063. valu = workingWord >>> 32 - bits; // :uint
  27064. // if size > 31, handle error
  27065. workingBitsAvailable -= bits;
  27066. if (workingBitsAvailable > 0) {
  27067. workingWord <<= bits;
  27068. } else if (workingBytesAvailable > 0) {
  27069. this.loadWord();
  27070. }
  27071. bits = size - bits;
  27072. if (bits > 0) {
  27073. return valu << bits | this.readBits(bits);
  27074. }
  27075. return valu;
  27076. }; // ():uint
  27077. this.skipLeadingZeros = function () {
  27078. var leadingZeroCount; // :uint
  27079. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  27080. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  27081. // the first bit of working word is 1
  27082. workingWord <<= leadingZeroCount;
  27083. workingBitsAvailable -= leadingZeroCount;
  27084. return leadingZeroCount;
  27085. }
  27086. } // we exhausted workingWord and still have not found a 1
  27087. this.loadWord();
  27088. return leadingZeroCount + this.skipLeadingZeros();
  27089. }; // ():void
  27090. this.skipUnsignedExpGolomb = function () {
  27091. this.skipBits(1 + this.skipLeadingZeros());
  27092. }; // ():void
  27093. this.skipExpGolomb = function () {
  27094. this.skipBits(1 + this.skipLeadingZeros());
  27095. }; // ():uint
  27096. this.readUnsignedExpGolomb = function () {
  27097. var clz = this.skipLeadingZeros(); // :uint
  27098. return this.readBits(clz + 1) - 1;
  27099. }; // ():int
  27100. this.readExpGolomb = function () {
  27101. var valu = this.readUnsignedExpGolomb(); // :int
  27102. if (0x01 & valu) {
  27103. // the number is odd if the low order bit is set
  27104. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  27105. }
  27106. return -1 * (valu >>> 1); // divide by two then make it negative
  27107. }; // Some convenience functions
  27108. // :Boolean
  27109. this.readBoolean = function () {
  27110. return this.readBits(1) === 1;
  27111. }; // ():int
  27112. this.readUnsignedByte = function () {
  27113. return this.readBits(8);
  27114. };
  27115. this.loadWord();
  27116. };
  27117. var expGolomb = ExpGolomb;
  27118. var _H264Stream, _NalByteStream;
  27119. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  27120. /**
  27121. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  27122. */
  27123. _NalByteStream = function NalByteStream() {
  27124. var syncPoint = 0,
  27125. i,
  27126. buffer;
  27127. _NalByteStream.prototype.init.call(this);
  27128. /*
  27129. * Scans a byte stream and triggers a data event with the NAL units found.
  27130. * @param {Object} data Event received from H264Stream
  27131. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  27132. *
  27133. * @see H264Stream.push
  27134. */
  27135. this.push = function (data) {
  27136. var swapBuffer;
  27137. if (!buffer) {
  27138. buffer = data.data;
  27139. } else {
  27140. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  27141. swapBuffer.set(buffer);
  27142. swapBuffer.set(data.data, buffer.byteLength);
  27143. buffer = swapBuffer;
  27144. } // Rec. ITU-T H.264, Annex B
  27145. // scan for NAL unit boundaries
  27146. // a match looks like this:
  27147. // 0 0 1 .. NAL .. 0 0 1
  27148. // ^ sync point ^ i
  27149. // or this:
  27150. // 0 0 1 .. NAL .. 0 0 0
  27151. // ^ sync point ^ i
  27152. // advance the sync point to a NAL start, if necessary
  27153. for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
  27154. if (buffer[syncPoint + 2] === 1) {
  27155. // the sync point is properly aligned
  27156. i = syncPoint + 5;
  27157. break;
  27158. }
  27159. }
  27160. while (i < buffer.byteLength) {
  27161. // look at the current byte to determine if we've hit the end of
  27162. // a NAL unit boundary
  27163. switch (buffer[i]) {
  27164. case 0:
  27165. // skip past non-sync sequences
  27166. if (buffer[i - 1] !== 0) {
  27167. i += 2;
  27168. break;
  27169. } else if (buffer[i - 2] !== 0) {
  27170. i++;
  27171. break;
  27172. } // deliver the NAL unit if it isn't empty
  27173. if (syncPoint + 3 !== i - 2) {
  27174. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  27175. } // drop trailing zeroes
  27176. do {
  27177. i++;
  27178. } while (buffer[i] !== 1 && i < buffer.length);
  27179. syncPoint = i - 2;
  27180. i += 3;
  27181. break;
  27182. case 1:
  27183. // skip past non-sync sequences
  27184. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  27185. i += 3;
  27186. break;
  27187. } // deliver the NAL unit
  27188. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  27189. syncPoint = i - 2;
  27190. i += 3;
  27191. break;
  27192. default:
  27193. // the current byte isn't a one or zero, so it cannot be part
  27194. // of a sync sequence
  27195. i += 3;
  27196. break;
  27197. }
  27198. } // filter out the NAL units that were delivered
  27199. buffer = buffer.subarray(syncPoint);
  27200. i -= syncPoint;
  27201. syncPoint = 0;
  27202. };
  27203. this.flush = function () {
  27204. // deliver the last buffered NAL unit
  27205. if (buffer && buffer.byteLength > 3) {
  27206. this.trigger('data', buffer.subarray(syncPoint + 3));
  27207. } // reset the stream state
  27208. buffer = null;
  27209. syncPoint = 0;
  27210. this.trigger('done');
  27211. };
  27212. };
  27213. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  27214. // see Recommendation ITU-T H.264 (4/2013),
  27215. // 7.3.2.1.1 Sequence parameter set data syntax
  27216. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  27217. 100: true,
  27218. 110: true,
  27219. 122: true,
  27220. 244: true,
  27221. 44: true,
  27222. 83: true,
  27223. 86: true,
  27224. 118: true,
  27225. 128: true,
  27226. 138: true,
  27227. 139: true,
  27228. 134: true
  27229. };
  27230. /**
  27231. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  27232. * events.
  27233. */
  27234. _H264Stream = function H264Stream() {
  27235. var nalByteStream = new _NalByteStream(),
  27236. self,
  27237. trackId,
  27238. currentPts,
  27239. currentDts,
  27240. discardEmulationPreventionBytes,
  27241. readSequenceParameterSet,
  27242. skipScalingList;
  27243. _H264Stream.prototype.init.call(this);
  27244. self = this;
  27245. /*
  27246. * Pushes a packet from a stream onto the NalByteStream
  27247. *
  27248. * @param {Object} packet - A packet received from a stream
  27249. * @param {Uint8Array} packet.data - The raw bytes of the packet
  27250. * @param {Number} packet.dts - Decode timestamp of the packet
  27251. * @param {Number} packet.pts - Presentation timestamp of the packet
  27252. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  27253. * @param {('video'|'audio')} packet.type - The type of packet
  27254. *
  27255. */
  27256. this.push = function (packet) {
  27257. if (packet.type !== 'video') {
  27258. return;
  27259. }
  27260. trackId = packet.trackId;
  27261. currentPts = packet.pts;
  27262. currentDts = packet.dts;
  27263. nalByteStream.push(packet);
  27264. };
  27265. /*
  27266. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  27267. * for the NALUs to the next stream component.
  27268. * Also, preprocess caption and sequence parameter NALUs.
  27269. *
  27270. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  27271. * @see NalByteStream.push
  27272. */
  27273. nalByteStream.on('data', function (data) {
  27274. var event = {
  27275. trackId: trackId,
  27276. pts: currentPts,
  27277. dts: currentDts,
  27278. data: data
  27279. };
  27280. switch (data[0] & 0x1f) {
  27281. case 0x05:
  27282. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  27283. break;
  27284. case 0x06:
  27285. event.nalUnitType = 'sei_rbsp';
  27286. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  27287. break;
  27288. case 0x07:
  27289. event.nalUnitType = 'seq_parameter_set_rbsp';
  27290. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  27291. event.config = readSequenceParameterSet(event.escapedRBSP);
  27292. break;
  27293. case 0x08:
  27294. event.nalUnitType = 'pic_parameter_set_rbsp';
  27295. break;
  27296. case 0x09:
  27297. event.nalUnitType = 'access_unit_delimiter_rbsp';
  27298. break;
  27299. default:
  27300. break;
  27301. } // This triggers data on the H264Stream
  27302. self.trigger('data', event);
  27303. });
  27304. nalByteStream.on('done', function () {
  27305. self.trigger('done');
  27306. });
  27307. this.flush = function () {
  27308. nalByteStream.flush();
  27309. };
  27310. /**
  27311. * Advance the ExpGolomb decoder past a scaling list. The scaling
  27312. * list is optionally transmitted as part of a sequence parameter
  27313. * set and is not relevant to transmuxing.
  27314. * @param count {number} the number of entries in this scaling list
  27315. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  27316. * start of a scaling list
  27317. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  27318. */
  27319. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  27320. var lastScale = 8,
  27321. nextScale = 8,
  27322. j,
  27323. deltaScale;
  27324. for (j = 0; j < count; j++) {
  27325. if (nextScale !== 0) {
  27326. deltaScale = expGolombDecoder.readExpGolomb();
  27327. nextScale = (lastScale + deltaScale + 256) % 256;
  27328. }
  27329. lastScale = nextScale === 0 ? lastScale : nextScale;
  27330. }
  27331. };
  27332. /**
  27333. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  27334. * Sequence Payload"
  27335. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  27336. * unit
  27337. * @return {Uint8Array} the RBSP without any Emulation
  27338. * Prevention Bytes
  27339. */
  27340. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  27341. var length = data.byteLength,
  27342. emulationPreventionBytesPositions = [],
  27343. i = 1,
  27344. newLength,
  27345. newData; // Find all `Emulation Prevention Bytes`
  27346. while (i < length - 2) {
  27347. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  27348. emulationPreventionBytesPositions.push(i + 2);
  27349. i += 2;
  27350. } else {
  27351. i++;
  27352. }
  27353. } // If no Emulation Prevention Bytes were found just return the original
  27354. // array
  27355. if (emulationPreventionBytesPositions.length === 0) {
  27356. return data;
  27357. } // Create a new array to hold the NAL unit data
  27358. newLength = length - emulationPreventionBytesPositions.length;
  27359. newData = new Uint8Array(newLength);
  27360. var sourceIndex = 0;
  27361. for (i = 0; i < newLength; sourceIndex++, i++) {
  27362. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  27363. // Skip this byte
  27364. sourceIndex++; // Remove this position index
  27365. emulationPreventionBytesPositions.shift();
  27366. }
  27367. newData[i] = data[sourceIndex];
  27368. }
  27369. return newData;
  27370. };
  27371. /**
  27372. * Read a sequence parameter set and return some interesting video
  27373. * properties. A sequence parameter set is the H264 metadata that
  27374. * describes the properties of upcoming video frames.
  27375. * @param data {Uint8Array} the bytes of a sequence parameter set
  27376. * @return {object} an object with configuration parsed from the
  27377. * sequence parameter set, including the dimensions of the
  27378. * associated video frames.
  27379. */
  27380. readSequenceParameterSet = function readSequenceParameterSet(data) {
  27381. var frameCropLeftOffset = 0,
  27382. frameCropRightOffset = 0,
  27383. frameCropTopOffset = 0,
  27384. frameCropBottomOffset = 0,
  27385. sarScale = 1,
  27386. expGolombDecoder,
  27387. profileIdc,
  27388. levelIdc,
  27389. profileCompatibility,
  27390. chromaFormatIdc,
  27391. picOrderCntType,
  27392. numRefFramesInPicOrderCntCycle,
  27393. picWidthInMbsMinus1,
  27394. picHeightInMapUnitsMinus1,
  27395. frameMbsOnlyFlag,
  27396. scalingListCount,
  27397. sarRatio,
  27398. aspectRatioIdc,
  27399. i;
  27400. expGolombDecoder = new expGolomb(data);
  27401. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  27402. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  27403. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  27404. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  27405. // some profiles have more optional data we don't need
  27406. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  27407. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  27408. if (chromaFormatIdc === 3) {
  27409. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  27410. }
  27411. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  27412. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  27413. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  27414. if (expGolombDecoder.readBoolean()) {
  27415. // seq_scaling_matrix_present_flag
  27416. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  27417. for (i = 0; i < scalingListCount; i++) {
  27418. if (expGolombDecoder.readBoolean()) {
  27419. // seq_scaling_list_present_flag[ i ]
  27420. if (i < 6) {
  27421. skipScalingList(16, expGolombDecoder);
  27422. } else {
  27423. skipScalingList(64, expGolombDecoder);
  27424. }
  27425. }
  27426. }
  27427. }
  27428. }
  27429. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  27430. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  27431. if (picOrderCntType === 0) {
  27432. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  27433. } else if (picOrderCntType === 1) {
  27434. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  27435. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  27436. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  27437. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  27438. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  27439. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  27440. }
  27441. }
  27442. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  27443. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  27444. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  27445. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  27446. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  27447. if (frameMbsOnlyFlag === 0) {
  27448. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  27449. }
  27450. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  27451. if (expGolombDecoder.readBoolean()) {
  27452. // frame_cropping_flag
  27453. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  27454. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  27455. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  27456. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  27457. }
  27458. if (expGolombDecoder.readBoolean()) {
  27459. // vui_parameters_present_flag
  27460. if (expGolombDecoder.readBoolean()) {
  27461. // aspect_ratio_info_present_flag
  27462. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  27463. switch (aspectRatioIdc) {
  27464. case 1:
  27465. sarRatio = [1, 1];
  27466. break;
  27467. case 2:
  27468. sarRatio = [12, 11];
  27469. break;
  27470. case 3:
  27471. sarRatio = [10, 11];
  27472. break;
  27473. case 4:
  27474. sarRatio = [16, 11];
  27475. break;
  27476. case 5:
  27477. sarRatio = [40, 33];
  27478. break;
  27479. case 6:
  27480. sarRatio = [24, 11];
  27481. break;
  27482. case 7:
  27483. sarRatio = [20, 11];
  27484. break;
  27485. case 8:
  27486. sarRatio = [32, 11];
  27487. break;
  27488. case 9:
  27489. sarRatio = [80, 33];
  27490. break;
  27491. case 10:
  27492. sarRatio = [18, 11];
  27493. break;
  27494. case 11:
  27495. sarRatio = [15, 11];
  27496. break;
  27497. case 12:
  27498. sarRatio = [64, 33];
  27499. break;
  27500. case 13:
  27501. sarRatio = [160, 99];
  27502. break;
  27503. case 14:
  27504. sarRatio = [4, 3];
  27505. break;
  27506. case 15:
  27507. sarRatio = [3, 2];
  27508. break;
  27509. case 16:
  27510. sarRatio = [2, 1];
  27511. break;
  27512. case 255:
  27513. {
  27514. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  27515. break;
  27516. }
  27517. }
  27518. if (sarRatio) {
  27519. sarScale = sarRatio[0] / sarRatio[1];
  27520. }
  27521. }
  27522. }
  27523. return {
  27524. profileIdc: profileIdc,
  27525. levelIdc: levelIdc,
  27526. profileCompatibility: profileCompatibility,
  27527. width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  27528. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
  27529. };
  27530. };
  27531. };
  27532. _H264Stream.prototype = new stream();
  27533. var h264 = {
  27534. H264Stream: _H264Stream,
  27535. NalByteStream: _NalByteStream
  27536. };
  27537. /**
  27538. * mux.js
  27539. *
  27540. * Copyright (c) 2016 Brightcove
  27541. * All rights reserved.
  27542. *
  27543. * Utilities to detect basic properties and metadata about Aac data.
  27544. */
  27545. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  27546. var isLikelyAacData = function isLikelyAacData(data) {
  27547. if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
  27548. return true;
  27549. }
  27550. return false;
  27551. };
  27552. var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  27553. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  27554. }; // return a percent-encoded representation of the specified byte range
  27555. // @see http://en.wikipedia.org/wiki/Percent-encoding
  27556. var percentEncode$1 = function percentEncode(bytes, start, end) {
  27557. var i,
  27558. result = '';
  27559. for (i = start; i < end; i++) {
  27560. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  27561. }
  27562. return result;
  27563. }; // return the string representation of the specified byte range,
  27564. // interpreted as ISO-8859-1.
  27565. var parseIso88591$1 = function parseIso88591(bytes, start, end) {
  27566. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  27567. };
  27568. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  27569. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  27570. flags = header[byteIndex + 5],
  27571. footerPresent = (flags & 16) >> 4;
  27572. if (footerPresent) {
  27573. return returnSize + 20;
  27574. }
  27575. return returnSize + 10;
  27576. };
  27577. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  27578. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  27579. middle = header[byteIndex + 4] << 3,
  27580. highTwo = header[byteIndex + 3] & 0x3 << 11;
  27581. return highTwo | middle | lowThree;
  27582. };
  27583. var parseType$1 = function parseType(header, byteIndex) {
  27584. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  27585. return 'timed-metadata';
  27586. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  27587. return 'audio';
  27588. }
  27589. return null;
  27590. };
  27591. var parseSampleRate = function parseSampleRate(packet) {
  27592. var i = 0;
  27593. while (i + 5 < packet.length) {
  27594. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  27595. // If a valid header was not found, jump one forward and attempt to
  27596. // find a valid ADTS header starting at the next byte
  27597. i++;
  27598. continue;
  27599. }
  27600. return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
  27601. }
  27602. return null;
  27603. };
  27604. var parseAacTimestamp = function parseAacTimestamp(packet) {
  27605. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  27606. frameStart = 10;
  27607. if (packet[5] & 0x40) {
  27608. // advance the frame start past the extended header
  27609. frameStart += 4; // header size field
  27610. frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
  27611. } // parse one or more ID3 frames
  27612. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  27613. do {
  27614. // determine the number of bytes in this frame
  27615. frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
  27616. if (frameSize < 1) {
  27617. return null;
  27618. }
  27619. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  27620. if (frameHeader === 'PRIV') {
  27621. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  27622. for (var i = 0; i < frame.byteLength; i++) {
  27623. if (frame[i] === 0) {
  27624. var owner = parseIso88591$1(frame, 0, i);
  27625. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  27626. var d = frame.subarray(i + 1);
  27627. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  27628. size *= 4;
  27629. size += d[7] & 0x03;
  27630. return size;
  27631. }
  27632. break;
  27633. }
  27634. }
  27635. }
  27636. frameStart += 10; // advance past the frame header
  27637. frameStart += frameSize; // advance past the frame body
  27638. } while (frameStart < packet.byteLength);
  27639. return null;
  27640. };
  27641. var utils = {
  27642. isLikelyAacData: isLikelyAacData,
  27643. parseId3TagSize: parseId3TagSize,
  27644. parseAdtsSize: parseAdtsSize,
  27645. parseType: parseType$1,
  27646. parseSampleRate: parseSampleRate,
  27647. parseAacTimestamp: parseAacTimestamp
  27648. }; // Constants
  27649. var _AacStream;
  27650. /**
  27651. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  27652. */
  27653. _AacStream = function AacStream() {
  27654. var everything = new Uint8Array(),
  27655. timeStamp = 0;
  27656. _AacStream.prototype.init.call(this);
  27657. this.setTimestamp = function (timestamp) {
  27658. timeStamp = timestamp;
  27659. };
  27660. this.push = function (bytes) {
  27661. var frameSize = 0,
  27662. byteIndex = 0,
  27663. bytesLeft,
  27664. chunk,
  27665. packet,
  27666. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  27667. // bytes that were pushed in
  27668. if (everything.length) {
  27669. tempLength = everything.length;
  27670. everything = new Uint8Array(bytes.byteLength + tempLength);
  27671. everything.set(everything.subarray(0, tempLength));
  27672. everything.set(bytes, tempLength);
  27673. } else {
  27674. everything = bytes;
  27675. }
  27676. while (everything.length - byteIndex >= 3) {
  27677. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  27678. // Exit early because we don't have enough to parse
  27679. // the ID3 tag header
  27680. if (everything.length - byteIndex < 10) {
  27681. break;
  27682. } // check framesize
  27683. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  27684. // to emit a full packet
  27685. // Add to byteIndex to support multiple ID3 tags in sequence
  27686. if (byteIndex + frameSize > everything.length) {
  27687. break;
  27688. }
  27689. chunk = {
  27690. type: 'timed-metadata',
  27691. data: everything.subarray(byteIndex, byteIndex + frameSize)
  27692. };
  27693. this.trigger('data', chunk);
  27694. byteIndex += frameSize;
  27695. continue;
  27696. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  27697. // Exit early because we don't have enough to parse
  27698. // the ADTS frame header
  27699. if (everything.length - byteIndex < 7) {
  27700. break;
  27701. }
  27702. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  27703. // to emit a full packet
  27704. if (byteIndex + frameSize > everything.length) {
  27705. break;
  27706. }
  27707. packet = {
  27708. type: 'audio',
  27709. data: everything.subarray(byteIndex, byteIndex + frameSize),
  27710. pts: timeStamp,
  27711. dts: timeStamp
  27712. };
  27713. this.trigger('data', packet);
  27714. byteIndex += frameSize;
  27715. continue;
  27716. }
  27717. byteIndex++;
  27718. }
  27719. bytesLeft = everything.length - byteIndex;
  27720. if (bytesLeft > 0) {
  27721. everything = everything.subarray(byteIndex);
  27722. } else {
  27723. everything = new Uint8Array();
  27724. }
  27725. };
  27726. };
  27727. _AacStream.prototype = new stream();
  27728. var aac = _AacStream;
  27729. var H264Stream = h264.H264Stream;
  27730. var isLikelyAacData$1 = utils.isLikelyAacData; // constants
  27731. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  27732. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
  27733. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  27734. /**
  27735. * Compare two arrays (even typed) for same-ness
  27736. */
  27737. var arrayEquals = function arrayEquals(a, b) {
  27738. var i;
  27739. if (a.length !== b.length) {
  27740. return false;
  27741. } // compare the value of each element in the array
  27742. for (i = 0; i < a.length; i++) {
  27743. if (a[i] !== b[i]) {
  27744. return false;
  27745. }
  27746. }
  27747. return true;
  27748. };
  27749. var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  27750. var ptsOffsetFromDts = startPts - startDts,
  27751. decodeDuration = endDts - startDts,
  27752. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  27753. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  27754. // In order to provide relevant values for the player times, base timing info on the
  27755. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  27756. return {
  27757. start: {
  27758. dts: baseMediaDecodeTime,
  27759. pts: baseMediaDecodeTime + ptsOffsetFromDts
  27760. },
  27761. end: {
  27762. dts: baseMediaDecodeTime + decodeDuration,
  27763. pts: baseMediaDecodeTime + presentationDuration
  27764. },
  27765. prependedContentDuration: prependedContentDuration,
  27766. baseMediaDecodeTime: baseMediaDecodeTime
  27767. };
  27768. };
  27769. /**
  27770. * Constructs a single-track, ISO BMFF media segment from AAC data
  27771. * events. The output of this stream can be fed to a SourceBuffer
  27772. * configured with a suitable initialization segment.
  27773. * @param track {object} track metadata configuration
  27774. * @param options {object} transmuxer options object
  27775. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  27776. * in the source; false to adjust the first segment to start at 0.
  27777. */
  27778. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  27779. var adtsFrames = [],
  27780. sequenceNumber = 0,
  27781. earliestAllowedDts = 0,
  27782. audioAppendStartTs = 0,
  27783. videoBaseMediaDecodeTime = Infinity;
  27784. options = options || {};
  27785. _AudioSegmentStream.prototype.init.call(this);
  27786. this.push = function (data) {
  27787. trackDecodeInfo.collectDtsInfo(track, data);
  27788. if (track) {
  27789. AUDIO_PROPERTIES.forEach(function (prop) {
  27790. track[prop] = data[prop];
  27791. });
  27792. } // buffer audio data until end() is called
  27793. adtsFrames.push(data);
  27794. };
  27795. this.setEarliestDts = function (earliestDts) {
  27796. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  27797. };
  27798. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  27799. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  27800. };
  27801. this.setAudioAppendStart = function (timestamp) {
  27802. audioAppendStartTs = timestamp;
  27803. };
  27804. this.flush = function () {
  27805. var frames, moof, mdat, boxes; // return early if no audio data has been observed
  27806. if (adtsFrames.length === 0) {
  27807. this.trigger('done', 'AudioSegmentStream');
  27808. return;
  27809. }
  27810. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  27811. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  27812. audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  27813. // samples (that is, adts frames) in the audio data
  27814. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  27815. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  27816. adtsFrames = [];
  27817. moof = mp4Generator.moof(sequenceNumber, [track]);
  27818. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  27819. sequenceNumber++;
  27820. boxes.set(moof);
  27821. boxes.set(mdat, moof.byteLength);
  27822. trackDecodeInfo.clearDtsInfo(track);
  27823. this.trigger('data', {
  27824. track: track,
  27825. boxes: boxes
  27826. });
  27827. this.trigger('done', 'AudioSegmentStream');
  27828. };
  27829. };
  27830. _AudioSegmentStream.prototype = new stream();
  27831. /**
  27832. * Constructs a single-track, ISO BMFF media segment from H264 data
  27833. * events. The output of this stream can be fed to a SourceBuffer
  27834. * configured with a suitable initialization segment.
  27835. * @param track {object} track metadata configuration
  27836. * @param options {object} transmuxer options object
  27837. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  27838. * gopsToAlignWith list when attempting to align gop pts
  27839. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  27840. * in the source; false to adjust the first segment to start at 0.
  27841. */
  27842. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  27843. var sequenceNumber = 0,
  27844. nalUnits = [],
  27845. gopsToAlignWith = [],
  27846. config,
  27847. pps;
  27848. options = options || {};
  27849. _VideoSegmentStream.prototype.init.call(this);
  27850. delete track.minPTS;
  27851. this.gopCache_ = [];
  27852. /**
  27853. * Constructs a ISO BMFF segment given H264 nalUnits
  27854. * @param {Object} nalUnit A data event representing a nalUnit
  27855. * @param {String} nalUnit.nalUnitType
  27856. * @param {Object} nalUnit.config Properties for a mp4 track
  27857. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  27858. * @see lib/codecs/h264.js
  27859. **/
  27860. this.push = function (nalUnit) {
  27861. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  27862. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  27863. config = nalUnit.config;
  27864. track.sps = [nalUnit.data];
  27865. VIDEO_PROPERTIES.forEach(function (prop) {
  27866. track[prop] = config[prop];
  27867. }, this);
  27868. }
  27869. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  27870. pps = nalUnit.data;
  27871. track.pps = [nalUnit.data];
  27872. } // buffer video until flush() is called
  27873. nalUnits.push(nalUnit);
  27874. };
  27875. /**
  27876. * Pass constructed ISO BMFF track and boxes on to the
  27877. * next stream in the pipeline
  27878. **/
  27879. this.flush = function () {
  27880. var frames,
  27881. gopForFusion,
  27882. gops,
  27883. moof,
  27884. mdat,
  27885. boxes,
  27886. prependedContentDuration = 0,
  27887. firstGop,
  27888. lastGop; // Throw away nalUnits at the start of the byte stream until
  27889. // we find the first AUD
  27890. while (nalUnits.length) {
  27891. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  27892. break;
  27893. }
  27894. nalUnits.shift();
  27895. } // Return early if no video data has been observed
  27896. if (nalUnits.length === 0) {
  27897. this.resetStream_();
  27898. this.trigger('done', 'VideoSegmentStream');
  27899. return;
  27900. } // Organize the raw nal-units into arrays that represent
  27901. // higher-level constructs such as frames and gops
  27902. // (group-of-pictures)
  27903. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  27904. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  27905. // a problem since MSE (on Chrome) requires a leading keyframe.
  27906. //
  27907. // We have two approaches to repairing this situation:
  27908. // 1) GOP-FUSION:
  27909. // This is where we keep track of the GOPS (group-of-pictures)
  27910. // from previous fragments and attempt to find one that we can
  27911. // prepend to the current fragment in order to create a valid
  27912. // fragment.
  27913. // 2) KEYFRAME-PULLING:
  27914. // Here we search for the first keyframe in the fragment and
  27915. // throw away all the frames between the start of the fragment
  27916. // and that keyframe. We then extend the duration and pull the
  27917. // PTS of the keyframe forward so that it covers the time range
  27918. // of the frames that were disposed of.
  27919. //
  27920. // #1 is far prefereable over #2 which can cause "stuttering" but
  27921. // requires more things to be just right.
  27922. if (!gops[0][0].keyFrame) {
  27923. // Search for a gop for fusion from our gopCache
  27924. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  27925. if (gopForFusion) {
  27926. // in order to provide more accurate timing information about the segment, save
  27927. // the number of seconds prepended to the original segment due to GOP fusion
  27928. prependedContentDuration = gopForFusion.duration;
  27929. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  27930. // new gop at the beginning
  27931. gops.byteLength += gopForFusion.byteLength;
  27932. gops.nalCount += gopForFusion.nalCount;
  27933. gops.pts = gopForFusion.pts;
  27934. gops.dts = gopForFusion.dts;
  27935. gops.duration += gopForFusion.duration;
  27936. } else {
  27937. // If we didn't find a candidate gop fall back to keyframe-pulling
  27938. gops = frameUtils.extendFirstKeyFrame(gops);
  27939. }
  27940. } // Trim gops to align with gopsToAlignWith
  27941. if (gopsToAlignWith.length) {
  27942. var alignedGops;
  27943. if (options.alignGopsAtEnd) {
  27944. alignedGops = this.alignGopsAtEnd_(gops);
  27945. } else {
  27946. alignedGops = this.alignGopsAtStart_(gops);
  27947. }
  27948. if (!alignedGops) {
  27949. // save all the nals in the last GOP into the gop cache
  27950. this.gopCache_.unshift({
  27951. gop: gops.pop(),
  27952. pps: track.pps,
  27953. sps: track.sps
  27954. }); // Keep a maximum of 6 GOPs in the cache
  27955. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  27956. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  27957. this.resetStream_();
  27958. this.trigger('done', 'VideoSegmentStream');
  27959. return;
  27960. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  27961. // when recalculated before sending off to CoalesceStream
  27962. trackDecodeInfo.clearDtsInfo(track);
  27963. gops = alignedGops;
  27964. }
  27965. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  27966. // samples (that is, frames) in the video data
  27967. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  27968. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  27969. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  27970. this.trigger('processedGopsInfo', gops.map(function (gop) {
  27971. return {
  27972. pts: gop.pts,
  27973. dts: gop.dts,
  27974. byteLength: gop.byteLength
  27975. };
  27976. }));
  27977. firstGop = gops[0];
  27978. lastGop = gops[gops.length - 1];
  27979. 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
  27980. this.gopCache_.unshift({
  27981. gop: gops.pop(),
  27982. pps: track.pps,
  27983. sps: track.sps
  27984. }); // Keep a maximum of 6 GOPs in the cache
  27985. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  27986. nalUnits = [];
  27987. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  27988. this.trigger('timelineStartInfo', track.timelineStartInfo);
  27989. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  27990. // throwing away hundreds of media segment fragments
  27991. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  27992. sequenceNumber++;
  27993. boxes.set(moof);
  27994. boxes.set(mdat, moof.byteLength);
  27995. this.trigger('data', {
  27996. track: track,
  27997. boxes: boxes
  27998. });
  27999. this.resetStream_(); // Continue with the flush process now
  28000. this.trigger('done', 'VideoSegmentStream');
  28001. };
  28002. this.resetStream_ = function () {
  28003. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  28004. // for instance, when we are rendition switching
  28005. config = undefined;
  28006. pps = undefined;
  28007. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  28008. // return it or return null if no good candidate was found
  28009. this.getGopForFusion_ = function (nalUnit) {
  28010. var halfSecond = 45000,
  28011. // Half-a-second in a 90khz clock
  28012. allowableOverlap = 10000,
  28013. // About 3 frames @ 30fps
  28014. nearestDistance = Infinity,
  28015. dtsDistance,
  28016. nearestGopObj,
  28017. currentGop,
  28018. currentGopObj,
  28019. i; // Search for the GOP nearest to the beginning of this nal unit
  28020. for (i = 0; i < this.gopCache_.length; i++) {
  28021. currentGopObj = this.gopCache_[i];
  28022. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  28023. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  28024. continue;
  28025. } // Reject Gops that would require a negative baseMediaDecodeTime
  28026. if (currentGop.dts < track.timelineStartInfo.dts) {
  28027. continue;
  28028. } // The distance between the end of the gop and the start of the nalUnit
  28029. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  28030. // a half-second of the nal unit
  28031. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  28032. // Always use the closest GOP we found if there is more than
  28033. // one candidate
  28034. if (!nearestGopObj || nearestDistance > dtsDistance) {
  28035. nearestGopObj = currentGopObj;
  28036. nearestDistance = dtsDistance;
  28037. }
  28038. }
  28039. }
  28040. if (nearestGopObj) {
  28041. return nearestGopObj.gop;
  28042. }
  28043. return null;
  28044. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  28045. // of gopsToAlignWith starting from the START of the list
  28046. this.alignGopsAtStart_ = function (gops) {
  28047. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  28048. byteLength = gops.byteLength;
  28049. nalCount = gops.nalCount;
  28050. duration = gops.duration;
  28051. alignIndex = gopIndex = 0;
  28052. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  28053. align = gopsToAlignWith[alignIndex];
  28054. gop = gops[gopIndex];
  28055. if (align.pts === gop.pts) {
  28056. break;
  28057. }
  28058. if (gop.pts > align.pts) {
  28059. // this current gop starts after the current gop we want to align on, so increment
  28060. // align index
  28061. alignIndex++;
  28062. continue;
  28063. } // current gop starts before the current gop we want to align on. so increment gop
  28064. // index
  28065. gopIndex++;
  28066. byteLength -= gop.byteLength;
  28067. nalCount -= gop.nalCount;
  28068. duration -= gop.duration;
  28069. }
  28070. if (gopIndex === 0) {
  28071. // no gops to trim
  28072. return gops;
  28073. }
  28074. if (gopIndex === gops.length) {
  28075. // all gops trimmed, skip appending all gops
  28076. return null;
  28077. }
  28078. alignedGops = gops.slice(gopIndex);
  28079. alignedGops.byteLength = byteLength;
  28080. alignedGops.duration = duration;
  28081. alignedGops.nalCount = nalCount;
  28082. alignedGops.pts = alignedGops[0].pts;
  28083. alignedGops.dts = alignedGops[0].dts;
  28084. return alignedGops;
  28085. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  28086. // of gopsToAlignWith starting from the END of the list
  28087. this.alignGopsAtEnd_ = function (gops) {
  28088. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  28089. alignIndex = gopsToAlignWith.length - 1;
  28090. gopIndex = gops.length - 1;
  28091. alignEndIndex = null;
  28092. matchFound = false;
  28093. while (alignIndex >= 0 && gopIndex >= 0) {
  28094. align = gopsToAlignWith[alignIndex];
  28095. gop = gops[gopIndex];
  28096. if (align.pts === gop.pts) {
  28097. matchFound = true;
  28098. break;
  28099. }
  28100. if (align.pts > gop.pts) {
  28101. alignIndex--;
  28102. continue;
  28103. }
  28104. if (alignIndex === gopsToAlignWith.length - 1) {
  28105. // gop.pts is greater than the last alignment candidate. If no match is found
  28106. // by the end of this loop, we still want to append gops that come after this
  28107. // point
  28108. alignEndIndex = gopIndex;
  28109. }
  28110. gopIndex--;
  28111. }
  28112. if (!matchFound && alignEndIndex === null) {
  28113. return null;
  28114. }
  28115. var trimIndex;
  28116. if (matchFound) {
  28117. trimIndex = gopIndex;
  28118. } else {
  28119. trimIndex = alignEndIndex;
  28120. }
  28121. if (trimIndex === 0) {
  28122. return gops;
  28123. }
  28124. var alignedGops = gops.slice(trimIndex);
  28125. var metadata = alignedGops.reduce(function (total, gop) {
  28126. total.byteLength += gop.byteLength;
  28127. total.duration += gop.duration;
  28128. total.nalCount += gop.nalCount;
  28129. return total;
  28130. }, {
  28131. byteLength: 0,
  28132. duration: 0,
  28133. nalCount: 0
  28134. });
  28135. alignedGops.byteLength = metadata.byteLength;
  28136. alignedGops.duration = metadata.duration;
  28137. alignedGops.nalCount = metadata.nalCount;
  28138. alignedGops.pts = alignedGops[0].pts;
  28139. alignedGops.dts = alignedGops[0].dts;
  28140. return alignedGops;
  28141. };
  28142. this.alignGopsWith = function (newGopsToAlignWith) {
  28143. gopsToAlignWith = newGopsToAlignWith;
  28144. };
  28145. };
  28146. _VideoSegmentStream.prototype = new stream();
  28147. /**
  28148. * A Stream that can combine multiple streams (ie. audio & video)
  28149. * into a single output segment for MSE. Also supports audio-only
  28150. * and video-only streams.
  28151. * @param options {object} transmuxer options object
  28152. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  28153. * in the source; false to adjust the first segment to start at media timeline start.
  28154. */
  28155. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  28156. // Number of Tracks per output segment
  28157. // If greater than 1, we combine multiple
  28158. // tracks into a single segment
  28159. this.numberOfTracks = 0;
  28160. this.metadataStream = metadataStream;
  28161. options = options || {};
  28162. if (typeof options.remux !== 'undefined') {
  28163. this.remuxTracks = !!options.remux;
  28164. } else {
  28165. this.remuxTracks = true;
  28166. }
  28167. if (typeof options.keepOriginalTimestamps === 'boolean') {
  28168. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  28169. }
  28170. this.pendingTracks = [];
  28171. this.videoTrack = null;
  28172. this.pendingBoxes = [];
  28173. this.pendingCaptions = [];
  28174. this.pendingMetadata = [];
  28175. this.pendingBytes = 0;
  28176. this.emittedTracks = 0;
  28177. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  28178. this.push = function (output) {
  28179. // buffer incoming captions until the associated video segment
  28180. // finishes
  28181. if (output.text) {
  28182. return this.pendingCaptions.push(output);
  28183. } // buffer incoming id3 tags until the final flush
  28184. if (output.frames) {
  28185. return this.pendingMetadata.push(output);
  28186. } // Add this track to the list of pending tracks and store
  28187. // important information required for the construction of
  28188. // the final segment
  28189. this.pendingTracks.push(output.track);
  28190. this.pendingBoxes.push(output.boxes);
  28191. this.pendingBytes += output.boxes.byteLength;
  28192. if (output.track.type === 'video') {
  28193. this.videoTrack = output.track;
  28194. }
  28195. if (output.track.type === 'audio') {
  28196. this.audioTrack = output.track;
  28197. }
  28198. };
  28199. };
  28200. _CoalesceStream.prototype = new stream();
  28201. _CoalesceStream.prototype.flush = function (flushSource) {
  28202. var offset = 0,
  28203. event = {
  28204. captions: [],
  28205. captionStreams: {},
  28206. metadata: [],
  28207. info: {}
  28208. },
  28209. caption,
  28210. id3,
  28211. initSegment,
  28212. timelineStartPts = 0,
  28213. i;
  28214. if (this.pendingTracks.length < this.numberOfTracks) {
  28215. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  28216. // Return because we haven't received a flush from a data-generating
  28217. // portion of the segment (meaning that we have only recieved meta-data
  28218. // or captions.)
  28219. return;
  28220. } else if (this.remuxTracks) {
  28221. // Return until we have enough tracks from the pipeline to remux (if we
  28222. // are remuxing audio and video into a single MP4)
  28223. return;
  28224. } else if (this.pendingTracks.length === 0) {
  28225. // In the case where we receive a flush without any data having been
  28226. // received we consider it an emitted track for the purposes of coalescing
  28227. // `done` events.
  28228. // We do this for the case where there is an audio and video track in the
  28229. // segment but no audio data. (seen in several playlists with alternate
  28230. // audio tracks and no audio present in the main TS segments.)
  28231. this.emittedTracks++;
  28232. if (this.emittedTracks >= this.numberOfTracks) {
  28233. this.trigger('done');
  28234. this.emittedTracks = 0;
  28235. }
  28236. return;
  28237. }
  28238. }
  28239. if (this.videoTrack) {
  28240. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  28241. VIDEO_PROPERTIES.forEach(function (prop) {
  28242. event.info[prop] = this.videoTrack[prop];
  28243. }, this);
  28244. } else if (this.audioTrack) {
  28245. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  28246. AUDIO_PROPERTIES.forEach(function (prop) {
  28247. event.info[prop] = this.audioTrack[prop];
  28248. }, this);
  28249. }
  28250. if (this.pendingTracks.length === 1) {
  28251. event.type = this.pendingTracks[0].type;
  28252. } else {
  28253. event.type = 'combined';
  28254. }
  28255. this.emittedTracks += this.pendingTracks.length;
  28256. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  28257. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  28258. // and track definitions
  28259. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  28260. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  28261. for (i = 0; i < this.pendingBoxes.length; i++) {
  28262. event.data.set(this.pendingBoxes[i], offset);
  28263. offset += this.pendingBoxes[i].byteLength;
  28264. } // Translate caption PTS times into second offsets to match the
  28265. // video timeline for the segment, and add track info
  28266. for (i = 0; i < this.pendingCaptions.length; i++) {
  28267. caption = this.pendingCaptions[i];
  28268. caption.startTime = caption.startPts;
  28269. if (!this.keepOriginalTimestamps) {
  28270. caption.startTime -= timelineStartPts;
  28271. }
  28272. caption.startTime /= 90e3;
  28273. caption.endTime = caption.endPts;
  28274. if (!this.keepOriginalTimestamps) {
  28275. caption.endTime -= timelineStartPts;
  28276. }
  28277. caption.endTime /= 90e3;
  28278. event.captionStreams[caption.stream] = true;
  28279. event.captions.push(caption);
  28280. } // Translate ID3 frame PTS times into second offsets to match the
  28281. // video timeline for the segment
  28282. for (i = 0; i < this.pendingMetadata.length; i++) {
  28283. id3 = this.pendingMetadata[i];
  28284. id3.cueTime = id3.pts;
  28285. if (!this.keepOriginalTimestamps) {
  28286. id3.cueTime -= timelineStartPts;
  28287. }
  28288. id3.cueTime /= 90e3;
  28289. event.metadata.push(id3);
  28290. } // We add this to every single emitted segment even though we only need
  28291. // it for the first
  28292. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  28293. this.pendingTracks.length = 0;
  28294. this.videoTrack = null;
  28295. this.pendingBoxes.length = 0;
  28296. this.pendingCaptions.length = 0;
  28297. this.pendingBytes = 0;
  28298. this.pendingMetadata.length = 0; // Emit the built segment
  28299. this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
  28300. if (this.emittedTracks >= this.numberOfTracks) {
  28301. this.trigger('done');
  28302. this.emittedTracks = 0;
  28303. }
  28304. };
  28305. /**
  28306. * A Stream that expects MP2T binary data as input and produces
  28307. * corresponding media segments, suitable for use with Media Source
  28308. * Extension (MSE) implementations that support the ISO BMFF byte
  28309. * stream format, like Chrome.
  28310. */
  28311. _Transmuxer = function Transmuxer(options) {
  28312. var self = this,
  28313. hasFlushed = true,
  28314. videoTrack,
  28315. audioTrack;
  28316. _Transmuxer.prototype.init.call(this);
  28317. options = options || {};
  28318. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  28319. this.transmuxPipeline_ = {};
  28320. this.setupAacPipeline = function () {
  28321. var pipeline = {};
  28322. this.transmuxPipeline_ = pipeline;
  28323. pipeline.type = 'aac';
  28324. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  28325. pipeline.aacStream = new aac();
  28326. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  28327. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  28328. pipeline.adtsStream = new adts();
  28329. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  28330. pipeline.headOfPipeline = pipeline.aacStream;
  28331. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  28332. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  28333. pipeline.metadataStream.on('timestamp', function (frame) {
  28334. pipeline.aacStream.setTimestamp(frame.timeStamp);
  28335. });
  28336. pipeline.aacStream.on('data', function (data) {
  28337. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  28338. audioTrack = audioTrack || {
  28339. timelineStartInfo: {
  28340. baseMediaDecodeTime: self.baseMediaDecodeTime
  28341. },
  28342. codec: 'adts',
  28343. type: 'audio'
  28344. }; // hook up the audio segment stream to the first track with aac data
  28345. pipeline.coalesceStream.numberOfTracks++;
  28346. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  28347. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  28348. }
  28349. }); // Re-emit any data coming from the coalesce stream to the outside world
  28350. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  28351. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  28352. };
  28353. this.setupTsPipeline = function () {
  28354. var pipeline = {};
  28355. this.transmuxPipeline_ = pipeline;
  28356. pipeline.type = 'ts';
  28357. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  28358. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  28359. pipeline.parseStream = new m2ts_1.TransportParseStream();
  28360. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  28361. pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  28362. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  28363. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  28364. pipeline.adtsStream = new adts();
  28365. pipeline.h264Stream = new H264Stream();
  28366. pipeline.captionStream = new m2ts_1.CaptionStream();
  28367. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  28368. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  28369. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  28370. // demux the streams
  28371. pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
  28372. pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  28373. pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  28374. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  28375. pipeline.elementaryStream.on('data', function (data) {
  28376. var i;
  28377. if (data.type === 'metadata') {
  28378. i = data.tracks.length; // scan the tracks listed in the metadata
  28379. while (i--) {
  28380. if (!videoTrack && data.tracks[i].type === 'video') {
  28381. videoTrack = data.tracks[i];
  28382. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  28383. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  28384. audioTrack = data.tracks[i];
  28385. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  28386. }
  28387. } // hook up the video segment stream to the first track with h264 data
  28388. if (videoTrack && !pipeline.videoSegmentStream) {
  28389. pipeline.coalesceStream.numberOfTracks++;
  28390. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  28391. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  28392. // When video emits timelineStartInfo data after a flush, we forward that
  28393. // info to the AudioSegmentStream, if it exists, because video timeline
  28394. // data takes precedence.
  28395. if (audioTrack) {
  28396. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  28397. // very earliest DTS we have seen in video because Chrome will
  28398. // interpret any video track with a baseMediaDecodeTime that is
  28399. // non-zero as a gap.
  28400. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  28401. }
  28402. });
  28403. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  28404. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  28405. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  28406. if (audioTrack) {
  28407. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  28408. }
  28409. }); // Set up the final part of the video pipeline
  28410. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  28411. }
  28412. if (audioTrack && !pipeline.audioSegmentStream) {
  28413. // hook up the audio segment stream to the first track with aac data
  28414. pipeline.coalesceStream.numberOfTracks++;
  28415. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  28416. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  28417. }
  28418. }
  28419. }); // Re-emit any data coming from the coalesce stream to the outside world
  28420. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  28421. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  28422. }; // hook up the segment streams once track metadata is delivered
  28423. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  28424. var pipeline = this.transmuxPipeline_;
  28425. if (!options.keepOriginalTimestamps) {
  28426. this.baseMediaDecodeTime = baseMediaDecodeTime;
  28427. }
  28428. if (audioTrack) {
  28429. audioTrack.timelineStartInfo.dts = undefined;
  28430. audioTrack.timelineStartInfo.pts = undefined;
  28431. trackDecodeInfo.clearDtsInfo(audioTrack);
  28432. if (!options.keepOriginalTimestamps) {
  28433. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  28434. }
  28435. if (pipeline.audioTimestampRolloverStream) {
  28436. pipeline.audioTimestampRolloverStream.discontinuity();
  28437. }
  28438. }
  28439. if (videoTrack) {
  28440. if (pipeline.videoSegmentStream) {
  28441. pipeline.videoSegmentStream.gopCache_ = [];
  28442. pipeline.videoTimestampRolloverStream.discontinuity();
  28443. }
  28444. videoTrack.timelineStartInfo.dts = undefined;
  28445. videoTrack.timelineStartInfo.pts = undefined;
  28446. trackDecodeInfo.clearDtsInfo(videoTrack);
  28447. pipeline.captionStream.reset();
  28448. if (!options.keepOriginalTimestamps) {
  28449. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  28450. }
  28451. }
  28452. if (pipeline.timedMetadataTimestampRolloverStream) {
  28453. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  28454. }
  28455. };
  28456. this.setAudioAppendStart = function (timestamp) {
  28457. if (audioTrack) {
  28458. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  28459. }
  28460. };
  28461. this.alignGopsWith = function (gopsToAlignWith) {
  28462. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  28463. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  28464. }
  28465. }; // feed incoming data to the front of the parsing pipeline
  28466. this.push = function (data) {
  28467. if (hasFlushed) {
  28468. var isAac = isLikelyAacData$1(data);
  28469. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  28470. this.setupAacPipeline();
  28471. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  28472. this.setupTsPipeline();
  28473. }
  28474. hasFlushed = false;
  28475. }
  28476. this.transmuxPipeline_.headOfPipeline.push(data);
  28477. }; // flush any buffered data
  28478. this.flush = function () {
  28479. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  28480. this.transmuxPipeline_.headOfPipeline.flush();
  28481. }; // Caption data has to be reset when seeking outside buffered range
  28482. this.resetCaptions = function () {
  28483. if (this.transmuxPipeline_.captionStream) {
  28484. this.transmuxPipeline_.captionStream.reset();
  28485. }
  28486. };
  28487. };
  28488. _Transmuxer.prototype = new stream();
  28489. var transmuxer = {
  28490. Transmuxer: _Transmuxer,
  28491. VideoSegmentStream: _VideoSegmentStream,
  28492. AudioSegmentStream: _AudioSegmentStream,
  28493. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  28494. VIDEO_PROPERTIES: VIDEO_PROPERTIES,
  28495. // exported for testing
  28496. generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
  28497. };
  28498. var inspectMp4,
  28499. _textifyMp,
  28500. parseType$2 = probe.parseType,
  28501. parseMp4Date = function parseMp4Date(seconds) {
  28502. return new Date(seconds * 1000 - 2082844800000);
  28503. },
  28504. parseSampleFlags = function parseSampleFlags(flags) {
  28505. return {
  28506. isLeading: (flags[0] & 0x0c) >>> 2,
  28507. dependsOn: flags[0] & 0x03,
  28508. isDependedOn: (flags[1] & 0xc0) >>> 6,
  28509. hasRedundancy: (flags[1] & 0x30) >>> 4,
  28510. paddingValue: (flags[1] & 0x0e) >>> 1,
  28511. isNonSyncSample: flags[1] & 0x01,
  28512. degradationPriority: flags[2] << 8 | flags[3]
  28513. };
  28514. },
  28515. nalParse = function nalParse(avcStream) {
  28516. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  28517. result = [],
  28518. i,
  28519. length;
  28520. for (i = 0; i + 4 < avcStream.length; i += length) {
  28521. length = avcView.getUint32(i);
  28522. i += 4; // bail if this doesn't appear to be an H264 stream
  28523. if (length <= 0) {
  28524. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  28525. continue;
  28526. }
  28527. switch (avcStream[i] & 0x1F) {
  28528. case 0x01:
  28529. result.push('slice_layer_without_partitioning_rbsp');
  28530. break;
  28531. case 0x05:
  28532. result.push('slice_layer_without_partitioning_rbsp_idr');
  28533. break;
  28534. case 0x06:
  28535. result.push('sei_rbsp');
  28536. break;
  28537. case 0x07:
  28538. result.push('seq_parameter_set_rbsp');
  28539. break;
  28540. case 0x08:
  28541. result.push('pic_parameter_set_rbsp');
  28542. break;
  28543. case 0x09:
  28544. result.push('access_unit_delimiter_rbsp');
  28545. break;
  28546. default:
  28547. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  28548. break;
  28549. }
  28550. }
  28551. return result;
  28552. },
  28553. // registry of handlers for individual mp4 box types
  28554. parse$$1 = {
  28555. // codingname, not a first-class box type. stsd entries share the
  28556. // same format as real boxes so the parsing infrastructure can be
  28557. // shared
  28558. avc1: function avc1(data) {
  28559. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28560. return {
  28561. dataReferenceIndex: view.getUint16(6),
  28562. width: view.getUint16(24),
  28563. height: view.getUint16(26),
  28564. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  28565. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  28566. frameCount: view.getUint16(40),
  28567. depth: view.getUint16(74),
  28568. config: inspectMp4(data.subarray(78, data.byteLength))
  28569. };
  28570. },
  28571. avcC: function avcC(data) {
  28572. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28573. result = {
  28574. configurationVersion: data[0],
  28575. avcProfileIndication: data[1],
  28576. profileCompatibility: data[2],
  28577. avcLevelIndication: data[3],
  28578. lengthSizeMinusOne: data[4] & 0x03,
  28579. sps: [],
  28580. pps: []
  28581. },
  28582. numOfSequenceParameterSets = data[5] & 0x1f,
  28583. numOfPictureParameterSets,
  28584. nalSize,
  28585. offset,
  28586. i; // iterate past any SPSs
  28587. offset = 6;
  28588. for (i = 0; i < numOfSequenceParameterSets; i++) {
  28589. nalSize = view.getUint16(offset);
  28590. offset += 2;
  28591. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  28592. offset += nalSize;
  28593. } // iterate past any PPSs
  28594. numOfPictureParameterSets = data[offset];
  28595. offset++;
  28596. for (i = 0; i < numOfPictureParameterSets; i++) {
  28597. nalSize = view.getUint16(offset);
  28598. offset += 2;
  28599. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  28600. offset += nalSize;
  28601. }
  28602. return result;
  28603. },
  28604. btrt: function btrt(data) {
  28605. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28606. return {
  28607. bufferSizeDB: view.getUint32(0),
  28608. maxBitrate: view.getUint32(4),
  28609. avgBitrate: view.getUint32(8)
  28610. };
  28611. },
  28612. esds: function esds(data) {
  28613. return {
  28614. version: data[0],
  28615. flags: new Uint8Array(data.subarray(1, 4)),
  28616. esId: data[6] << 8 | data[7],
  28617. streamPriority: data[8] & 0x1f,
  28618. decoderConfig: {
  28619. objectProfileIndication: data[11],
  28620. streamType: data[12] >>> 2 & 0x3f,
  28621. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  28622. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  28623. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  28624. decoderConfigDescriptor: {
  28625. tag: data[24],
  28626. length: data[25],
  28627. audioObjectType: data[26] >>> 3 & 0x1f,
  28628. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  28629. channelConfiguration: data[27] >>> 3 & 0x0f
  28630. }
  28631. }
  28632. };
  28633. },
  28634. ftyp: function ftyp(data) {
  28635. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28636. result = {
  28637. majorBrand: parseType$2(data.subarray(0, 4)),
  28638. minorVersion: view.getUint32(4),
  28639. compatibleBrands: []
  28640. },
  28641. i = 8;
  28642. while (i < data.byteLength) {
  28643. result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
  28644. i += 4;
  28645. }
  28646. return result;
  28647. },
  28648. dinf: function dinf(data) {
  28649. return {
  28650. boxes: inspectMp4(data)
  28651. };
  28652. },
  28653. dref: function dref(data) {
  28654. return {
  28655. version: data[0],
  28656. flags: new Uint8Array(data.subarray(1, 4)),
  28657. dataReferences: inspectMp4(data.subarray(8))
  28658. };
  28659. },
  28660. hdlr: function hdlr(data) {
  28661. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28662. result = {
  28663. version: view.getUint8(0),
  28664. flags: new Uint8Array(data.subarray(1, 4)),
  28665. handlerType: parseType$2(data.subarray(8, 12)),
  28666. name: ''
  28667. },
  28668. i = 8; // parse out the name field
  28669. for (i = 24; i < data.byteLength; i++) {
  28670. if (data[i] === 0x00) {
  28671. // the name field is null-terminated
  28672. i++;
  28673. break;
  28674. }
  28675. result.name += String.fromCharCode(data[i]);
  28676. } // decode UTF-8 to javascript's internal representation
  28677. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  28678. result.name = decodeURIComponent(escape(result.name));
  28679. return result;
  28680. },
  28681. mdat: function mdat(data) {
  28682. return {
  28683. byteLength: data.byteLength,
  28684. nals: nalParse(data)
  28685. };
  28686. },
  28687. mdhd: function mdhd(data) {
  28688. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28689. i = 4,
  28690. language,
  28691. result = {
  28692. version: view.getUint8(0),
  28693. flags: new Uint8Array(data.subarray(1, 4)),
  28694. language: ''
  28695. };
  28696. if (result.version === 1) {
  28697. i += 4;
  28698. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28699. i += 8;
  28700. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28701. i += 4;
  28702. result.timescale = view.getUint32(i);
  28703. i += 8;
  28704. result.duration = view.getUint32(i); // truncating top 4 bytes
  28705. } else {
  28706. result.creationTime = parseMp4Date(view.getUint32(i));
  28707. i += 4;
  28708. result.modificationTime = parseMp4Date(view.getUint32(i));
  28709. i += 4;
  28710. result.timescale = view.getUint32(i);
  28711. i += 4;
  28712. result.duration = view.getUint32(i);
  28713. }
  28714. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  28715. // each field is the packed difference between its ASCII value and 0x60
  28716. language = view.getUint16(i);
  28717. result.language += String.fromCharCode((language >> 10) + 0x60);
  28718. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  28719. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  28720. return result;
  28721. },
  28722. mdia: function mdia(data) {
  28723. return {
  28724. boxes: inspectMp4(data)
  28725. };
  28726. },
  28727. mfhd: function mfhd(data) {
  28728. return {
  28729. version: data[0],
  28730. flags: new Uint8Array(data.subarray(1, 4)),
  28731. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  28732. };
  28733. },
  28734. minf: function minf(data) {
  28735. return {
  28736. boxes: inspectMp4(data)
  28737. };
  28738. },
  28739. // codingname, not a first-class box type. stsd entries share the
  28740. // same format as real boxes so the parsing infrastructure can be
  28741. // shared
  28742. mp4a: function mp4a(data) {
  28743. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28744. result = {
  28745. // 6 bytes reserved
  28746. dataReferenceIndex: view.getUint16(6),
  28747. // 4 + 4 bytes reserved
  28748. channelcount: view.getUint16(16),
  28749. samplesize: view.getUint16(18),
  28750. // 2 bytes pre_defined
  28751. // 2 bytes reserved
  28752. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  28753. }; // if there are more bytes to process, assume this is an ISO/IEC
  28754. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  28755. if (data.byteLength > 28) {
  28756. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  28757. }
  28758. return result;
  28759. },
  28760. moof: function moof(data) {
  28761. return {
  28762. boxes: inspectMp4(data)
  28763. };
  28764. },
  28765. moov: function moov(data) {
  28766. return {
  28767. boxes: inspectMp4(data)
  28768. };
  28769. },
  28770. mvex: function mvex(data) {
  28771. return {
  28772. boxes: inspectMp4(data)
  28773. };
  28774. },
  28775. mvhd: function mvhd(data) {
  28776. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28777. i = 4,
  28778. result = {
  28779. version: view.getUint8(0),
  28780. flags: new Uint8Array(data.subarray(1, 4))
  28781. };
  28782. if (result.version === 1) {
  28783. i += 4;
  28784. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28785. i += 8;
  28786. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28787. i += 4;
  28788. result.timescale = view.getUint32(i);
  28789. i += 8;
  28790. result.duration = view.getUint32(i); // truncating top 4 bytes
  28791. } else {
  28792. result.creationTime = parseMp4Date(view.getUint32(i));
  28793. i += 4;
  28794. result.modificationTime = parseMp4Date(view.getUint32(i));
  28795. i += 4;
  28796. result.timescale = view.getUint32(i);
  28797. i += 4;
  28798. result.duration = view.getUint32(i);
  28799. }
  28800. i += 4; // convert fixed-point, base 16 back to a number
  28801. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  28802. i += 4;
  28803. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  28804. i += 2;
  28805. i += 2;
  28806. i += 2 * 4;
  28807. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  28808. i += 9 * 4;
  28809. i += 6 * 4;
  28810. result.nextTrackId = view.getUint32(i);
  28811. return result;
  28812. },
  28813. pdin: function pdin(data) {
  28814. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28815. return {
  28816. version: view.getUint8(0),
  28817. flags: new Uint8Array(data.subarray(1, 4)),
  28818. rate: view.getUint32(4),
  28819. initialDelay: view.getUint32(8)
  28820. };
  28821. },
  28822. sdtp: function sdtp(data) {
  28823. var result = {
  28824. version: data[0],
  28825. flags: new Uint8Array(data.subarray(1, 4)),
  28826. samples: []
  28827. },
  28828. i;
  28829. for (i = 4; i < data.byteLength; i++) {
  28830. result.samples.push({
  28831. dependsOn: (data[i] & 0x30) >> 4,
  28832. isDependedOn: (data[i] & 0x0c) >> 2,
  28833. hasRedundancy: data[i] & 0x03
  28834. });
  28835. }
  28836. return result;
  28837. },
  28838. sidx: function sidx(data) {
  28839. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28840. result = {
  28841. version: data[0],
  28842. flags: new Uint8Array(data.subarray(1, 4)),
  28843. references: [],
  28844. referenceId: view.getUint32(4),
  28845. timescale: view.getUint32(8),
  28846. earliestPresentationTime: view.getUint32(12),
  28847. firstOffset: view.getUint32(16)
  28848. },
  28849. referenceCount = view.getUint16(22),
  28850. i;
  28851. for (i = 24; referenceCount; i += 12, referenceCount--) {
  28852. result.references.push({
  28853. referenceType: (data[i] & 0x80) >>> 7,
  28854. referencedSize: view.getUint32(i) & 0x7FFFFFFF,
  28855. subsegmentDuration: view.getUint32(i + 4),
  28856. startsWithSap: !!(data[i + 8] & 0x80),
  28857. sapType: (data[i + 8] & 0x70) >>> 4,
  28858. sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
  28859. });
  28860. }
  28861. return result;
  28862. },
  28863. smhd: function smhd(data) {
  28864. return {
  28865. version: data[0],
  28866. flags: new Uint8Array(data.subarray(1, 4)),
  28867. balance: data[4] + data[5] / 256
  28868. };
  28869. },
  28870. stbl: function stbl(data) {
  28871. return {
  28872. boxes: inspectMp4(data)
  28873. };
  28874. },
  28875. stco: function stco(data) {
  28876. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28877. result = {
  28878. version: data[0],
  28879. flags: new Uint8Array(data.subarray(1, 4)),
  28880. chunkOffsets: []
  28881. },
  28882. entryCount = view.getUint32(4),
  28883. i;
  28884. for (i = 8; entryCount; i += 4, entryCount--) {
  28885. result.chunkOffsets.push(view.getUint32(i));
  28886. }
  28887. return result;
  28888. },
  28889. stsc: function stsc(data) {
  28890. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28891. entryCount = view.getUint32(4),
  28892. result = {
  28893. version: data[0],
  28894. flags: new Uint8Array(data.subarray(1, 4)),
  28895. sampleToChunks: []
  28896. },
  28897. i;
  28898. for (i = 8; entryCount; i += 12, entryCount--) {
  28899. result.sampleToChunks.push({
  28900. firstChunk: view.getUint32(i),
  28901. samplesPerChunk: view.getUint32(i + 4),
  28902. sampleDescriptionIndex: view.getUint32(i + 8)
  28903. });
  28904. }
  28905. return result;
  28906. },
  28907. stsd: function stsd(data) {
  28908. return {
  28909. version: data[0],
  28910. flags: new Uint8Array(data.subarray(1, 4)),
  28911. sampleDescriptions: inspectMp4(data.subarray(8))
  28912. };
  28913. },
  28914. stsz: function stsz(data) {
  28915. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28916. result = {
  28917. version: data[0],
  28918. flags: new Uint8Array(data.subarray(1, 4)),
  28919. sampleSize: view.getUint32(4),
  28920. entries: []
  28921. },
  28922. i;
  28923. for (i = 12; i < data.byteLength; i += 4) {
  28924. result.entries.push(view.getUint32(i));
  28925. }
  28926. return result;
  28927. },
  28928. stts: function stts(data) {
  28929. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28930. result = {
  28931. version: data[0],
  28932. flags: new Uint8Array(data.subarray(1, 4)),
  28933. timeToSamples: []
  28934. },
  28935. entryCount = view.getUint32(4),
  28936. i;
  28937. for (i = 8; entryCount; i += 8, entryCount--) {
  28938. result.timeToSamples.push({
  28939. sampleCount: view.getUint32(i),
  28940. sampleDelta: view.getUint32(i + 4)
  28941. });
  28942. }
  28943. return result;
  28944. },
  28945. styp: function styp(data) {
  28946. return parse$$1.ftyp(data);
  28947. },
  28948. tfdt: function tfdt(data) {
  28949. var result = {
  28950. version: data[0],
  28951. flags: new Uint8Array(data.subarray(1, 4)),
  28952. baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  28953. };
  28954. if (result.version === 1) {
  28955. result.baseMediaDecodeTime *= Math.pow(2, 32);
  28956. result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
  28957. }
  28958. return result;
  28959. },
  28960. tfhd: function tfhd(data) {
  28961. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28962. result = {
  28963. version: data[0],
  28964. flags: new Uint8Array(data.subarray(1, 4)),
  28965. trackId: view.getUint32(4)
  28966. },
  28967. baseDataOffsetPresent = result.flags[2] & 0x01,
  28968. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  28969. defaultSampleDurationPresent = result.flags[2] & 0x08,
  28970. defaultSampleSizePresent = result.flags[2] & 0x10,
  28971. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  28972. durationIsEmpty = result.flags[0] & 0x010000,
  28973. defaultBaseIsMoof = result.flags[0] & 0x020000,
  28974. i;
  28975. i = 8;
  28976. if (baseDataOffsetPresent) {
  28977. i += 4; // truncate top 4 bytes
  28978. // FIXME: should we read the full 64 bits?
  28979. result.baseDataOffset = view.getUint32(12);
  28980. i += 4;
  28981. }
  28982. if (sampleDescriptionIndexPresent) {
  28983. result.sampleDescriptionIndex = view.getUint32(i);
  28984. i += 4;
  28985. }
  28986. if (defaultSampleDurationPresent) {
  28987. result.defaultSampleDuration = view.getUint32(i);
  28988. i += 4;
  28989. }
  28990. if (defaultSampleSizePresent) {
  28991. result.defaultSampleSize = view.getUint32(i);
  28992. i += 4;
  28993. }
  28994. if (defaultSampleFlagsPresent) {
  28995. result.defaultSampleFlags = view.getUint32(i);
  28996. }
  28997. if (durationIsEmpty) {
  28998. result.durationIsEmpty = true;
  28999. }
  29000. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  29001. result.baseDataOffsetIsMoof = true;
  29002. }
  29003. return result;
  29004. },
  29005. tkhd: function tkhd(data) {
  29006. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  29007. i = 4,
  29008. result = {
  29009. version: view.getUint8(0),
  29010. flags: new Uint8Array(data.subarray(1, 4))
  29011. };
  29012. if (result.version === 1) {
  29013. i += 4;
  29014. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  29015. i += 8;
  29016. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  29017. i += 4;
  29018. result.trackId = view.getUint32(i);
  29019. i += 4;
  29020. i += 8;
  29021. result.duration = view.getUint32(i); // truncating top 4 bytes
  29022. } else {
  29023. result.creationTime = parseMp4Date(view.getUint32(i));
  29024. i += 4;
  29025. result.modificationTime = parseMp4Date(view.getUint32(i));
  29026. i += 4;
  29027. result.trackId = view.getUint32(i);
  29028. i += 4;
  29029. i += 4;
  29030. result.duration = view.getUint32(i);
  29031. }
  29032. i += 4;
  29033. i += 2 * 4;
  29034. result.layer = view.getUint16(i);
  29035. i += 2;
  29036. result.alternateGroup = view.getUint16(i);
  29037. i += 2; // convert fixed-point, base 16 back to a number
  29038. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  29039. i += 2;
  29040. i += 2;
  29041. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  29042. i += 9 * 4;
  29043. result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
  29044. i += 4;
  29045. result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
  29046. return result;
  29047. },
  29048. traf: function traf(data) {
  29049. return {
  29050. boxes: inspectMp4(data)
  29051. };
  29052. },
  29053. trak: function trak(data) {
  29054. return {
  29055. boxes: inspectMp4(data)
  29056. };
  29057. },
  29058. trex: function trex(data) {
  29059. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  29060. return {
  29061. version: data[0],
  29062. flags: new Uint8Array(data.subarray(1, 4)),
  29063. trackId: view.getUint32(4),
  29064. defaultSampleDescriptionIndex: view.getUint32(8),
  29065. defaultSampleDuration: view.getUint32(12),
  29066. defaultSampleSize: view.getUint32(16),
  29067. sampleDependsOn: data[20] & 0x03,
  29068. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  29069. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  29070. samplePaddingValue: (data[21] & 0x0e) >> 1,
  29071. sampleIsDifferenceSample: !!(data[21] & 0x01),
  29072. sampleDegradationPriority: view.getUint16(22)
  29073. };
  29074. },
  29075. trun: function trun(data) {
  29076. var result = {
  29077. version: data[0],
  29078. flags: new Uint8Array(data.subarray(1, 4)),
  29079. samples: []
  29080. },
  29081. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  29082. // Flag interpretation
  29083. dataOffsetPresent = result.flags[2] & 0x01,
  29084. // compare with 2nd byte of 0x1
  29085. firstSampleFlagsPresent = result.flags[2] & 0x04,
  29086. // compare with 2nd byte of 0x4
  29087. sampleDurationPresent = result.flags[1] & 0x01,
  29088. // compare with 2nd byte of 0x100
  29089. sampleSizePresent = result.flags[1] & 0x02,
  29090. // compare with 2nd byte of 0x200
  29091. sampleFlagsPresent = result.flags[1] & 0x04,
  29092. // compare with 2nd byte of 0x400
  29093. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  29094. // compare with 2nd byte of 0x800
  29095. sampleCount = view.getUint32(4),
  29096. offset = 8,
  29097. sample;
  29098. if (dataOffsetPresent) {
  29099. // 32 bit signed integer
  29100. result.dataOffset = view.getInt32(offset);
  29101. offset += 4;
  29102. } // Overrides the flags for the first sample only. The order of
  29103. // optional values will be: duration, size, compositionTimeOffset
  29104. if (firstSampleFlagsPresent && sampleCount) {
  29105. sample = {
  29106. flags: parseSampleFlags(data.subarray(offset, offset + 4))
  29107. };
  29108. offset += 4;
  29109. if (sampleDurationPresent) {
  29110. sample.duration = view.getUint32(offset);
  29111. offset += 4;
  29112. }
  29113. if (sampleSizePresent) {
  29114. sample.size = view.getUint32(offset);
  29115. offset += 4;
  29116. }
  29117. if (sampleCompositionTimeOffsetPresent) {
  29118. // Note: this should be a signed int if version is 1
  29119. sample.compositionTimeOffset = view.getUint32(offset);
  29120. offset += 4;
  29121. }
  29122. result.samples.push(sample);
  29123. sampleCount--;
  29124. }
  29125. while (sampleCount--) {
  29126. sample = {};
  29127. if (sampleDurationPresent) {
  29128. sample.duration = view.getUint32(offset);
  29129. offset += 4;
  29130. }
  29131. if (sampleSizePresent) {
  29132. sample.size = view.getUint32(offset);
  29133. offset += 4;
  29134. }
  29135. if (sampleFlagsPresent) {
  29136. sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
  29137. offset += 4;
  29138. }
  29139. if (sampleCompositionTimeOffsetPresent) {
  29140. // Note: this should be a signed int if version is 1
  29141. sample.compositionTimeOffset = view.getUint32(offset);
  29142. offset += 4;
  29143. }
  29144. result.samples.push(sample);
  29145. }
  29146. return result;
  29147. },
  29148. 'url ': function url(data) {
  29149. return {
  29150. version: data[0],
  29151. flags: new Uint8Array(data.subarray(1, 4))
  29152. };
  29153. },
  29154. vmhd: function vmhd(data) {
  29155. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  29156. return {
  29157. version: data[0],
  29158. flags: new Uint8Array(data.subarray(1, 4)),
  29159. graphicsmode: view.getUint16(4),
  29160. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  29161. };
  29162. }
  29163. };
  29164. /**
  29165. * Return a javascript array of box objects parsed from an ISO base
  29166. * media file.
  29167. * @param data {Uint8Array} the binary data of the media to be inspected
  29168. * @return {array} a javascript array of potentially nested box objects
  29169. */
  29170. inspectMp4 = function inspectMp4(data) {
  29171. var i = 0,
  29172. result = [],
  29173. view,
  29174. size,
  29175. type,
  29176. end,
  29177. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  29178. var ab = new ArrayBuffer(data.length);
  29179. var v = new Uint8Array(ab);
  29180. for (var z = 0; z < data.length; ++z) {
  29181. v[z] = data[z];
  29182. }
  29183. view = new DataView(ab);
  29184. while (i < data.byteLength) {
  29185. // parse box data
  29186. size = view.getUint32(i);
  29187. type = parseType$2(data.subarray(i + 4, i + 8));
  29188. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  29189. box = (parse$$1[type] || function (data) {
  29190. return {
  29191. data: data
  29192. };
  29193. })(data.subarray(i + 8, end));
  29194. box.size = size;
  29195. box.type = type; // store this box and move to the next
  29196. result.push(box);
  29197. i = end;
  29198. }
  29199. return result;
  29200. };
  29201. /**
  29202. * Returns a textual representation of the javascript represtentation
  29203. * of an MP4 file. You can use it as an alternative to
  29204. * JSON.stringify() to compare inspected MP4s.
  29205. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  29206. * file
  29207. * @param depth {number} (optional) the number of ancestor boxes of
  29208. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  29209. * @return {string} a text representation of the parsed MP4
  29210. */
  29211. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  29212. var indent;
  29213. depth = depth || 0;
  29214. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  29215. return inspectedMp4.map(function (box, index) {
  29216. // list the box type first at the current indentation level
  29217. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  29218. Object.keys(box).filter(function (key) {
  29219. return key !== 'type' && key !== 'boxes'; // output all the box properties
  29220. }).map(function (key) {
  29221. var prefix = indent + ' ' + key + ': ',
  29222. value = box[key]; // print out raw bytes as hexademical
  29223. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  29224. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
  29225. return ' ' + ('00' + byte.toString(16)).slice(-2);
  29226. }).join('').match(/.{1,24}/g);
  29227. if (!bytes) {
  29228. return prefix + '<>';
  29229. }
  29230. if (bytes.length === 1) {
  29231. return prefix + '<' + bytes.join('').slice(1) + '>';
  29232. }
  29233. return prefix + '<\n' + bytes.map(function (line) {
  29234. return indent + ' ' + line;
  29235. }).join('\n') + '\n' + indent + ' >';
  29236. } // stringify generic objects
  29237. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  29238. if (index === 0) {
  29239. return line;
  29240. }
  29241. return indent + ' ' + line;
  29242. }).join('\n');
  29243. }).join('\n') + ( // recursively textify the child boxes
  29244. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  29245. }).join('\n');
  29246. };
  29247. var mp4Inspector$$1 = {
  29248. inspect: inspectMp4,
  29249. textify: _textifyMp,
  29250. parseTfdt: parse$$1.tfdt,
  29251. parseHdlr: parse$$1.hdlr,
  29252. parseTfhd: parse$$1.tfhd,
  29253. parseTrun: parse$$1.trun,
  29254. parseSidx: parse$$1.sidx
  29255. };
  29256. var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
  29257. var CaptionStream$1 = captionStream.CaptionStream;
  29258. /**
  29259. * Maps an offset in the mdat to a sample based on the the size of the samples.
  29260. * Assumes that `parseSamples` has been called first.
  29261. *
  29262. * @param {Number} offset - The offset into the mdat
  29263. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  29264. * @return {?Object} The matching sample, or null if no match was found.
  29265. *
  29266. * @see ISO-BMFF-12/2015, Section 8.8.8
  29267. **/
  29268. var mapToSample = function mapToSample(offset, samples) {
  29269. var approximateOffset = offset;
  29270. for (var i = 0; i < samples.length; i++) {
  29271. var sample = samples[i];
  29272. if (approximateOffset < sample.size) {
  29273. return sample;
  29274. }
  29275. approximateOffset -= sample.size;
  29276. }
  29277. return null;
  29278. };
  29279. /**
  29280. * Finds SEI nal units contained in a Media Data Box.
  29281. * Assumes that `parseSamples` has been called first.
  29282. *
  29283. * @param {Uint8Array} avcStream - The bytes of the mdat
  29284. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  29285. * @param {Number} trackId - The trackId of this video track
  29286. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  29287. * The contents of the seiNal should match what is expected by
  29288. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  29289. *
  29290. * @see ISO-BMFF-12/2015, Section 8.1.1
  29291. * @see Rec. ITU-T H.264, 7.3.2.3.1
  29292. **/
  29293. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  29294. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  29295. result = [],
  29296. seiNal,
  29297. i,
  29298. length,
  29299. lastMatchedSample;
  29300. for (i = 0; i + 4 < avcStream.length; i += length) {
  29301. length = avcView.getUint32(i);
  29302. i += 4; // Bail if this doesn't appear to be an H264 stream
  29303. if (length <= 0) {
  29304. continue;
  29305. }
  29306. switch (avcStream[i] & 0x1F) {
  29307. case 0x06:
  29308. var data = avcStream.subarray(i + 1, i + 1 + length);
  29309. var matchingSample = mapToSample(i, samples);
  29310. seiNal = {
  29311. nalUnitType: 'sei_rbsp',
  29312. size: length,
  29313. data: data,
  29314. escapedRBSP: discardEmulationPreventionBytes$1(data),
  29315. trackId: trackId
  29316. };
  29317. if (matchingSample) {
  29318. seiNal.pts = matchingSample.pts;
  29319. seiNal.dts = matchingSample.dts;
  29320. lastMatchedSample = matchingSample;
  29321. } else {
  29322. // If a matching sample cannot be found, use the last
  29323. // sample's values as they should be as close as possible
  29324. seiNal.pts = lastMatchedSample.pts;
  29325. seiNal.dts = lastMatchedSample.dts;
  29326. }
  29327. result.push(seiNal);
  29328. break;
  29329. default:
  29330. break;
  29331. }
  29332. }
  29333. return result;
  29334. };
  29335. /**
  29336. * Parses sample information out of Track Run Boxes and calculates
  29337. * the absolute presentation and decode timestamps of each sample.
  29338. *
  29339. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  29340. * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
  29341. @see ISO-BMFF-12/2015, Section 8.8.12
  29342. * @param {Object} tfhd - The parsed Track Fragment Header
  29343. * @see inspect.parseTfhd
  29344. * @return {Object[]} the parsed samples
  29345. *
  29346. * @see ISO-BMFF-12/2015, Section 8.8.8
  29347. **/
  29348. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  29349. var currentDts = baseMediaDecodeTime;
  29350. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  29351. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  29352. var trackId = tfhd.trackId;
  29353. var allSamples = [];
  29354. truns.forEach(function (trun) {
  29355. // Note: We currently do not parse the sample table as well
  29356. // as the trun. It's possible some sources will require this.
  29357. // moov > trak > mdia > minf > stbl
  29358. var trackRun = mp4Inspector$$1.parseTrun(trun);
  29359. var samples = trackRun.samples;
  29360. samples.forEach(function (sample) {
  29361. if (sample.duration === undefined) {
  29362. sample.duration = defaultSampleDuration;
  29363. }
  29364. if (sample.size === undefined) {
  29365. sample.size = defaultSampleSize;
  29366. }
  29367. sample.trackId = trackId;
  29368. sample.dts = currentDts;
  29369. if (sample.compositionTimeOffset === undefined) {
  29370. sample.compositionTimeOffset = 0;
  29371. }
  29372. sample.pts = currentDts + sample.compositionTimeOffset;
  29373. currentDts += sample.duration;
  29374. });
  29375. allSamples = allSamples.concat(samples);
  29376. });
  29377. return allSamples;
  29378. };
  29379. /**
  29380. * Parses out caption nals from an FMP4 segment's video tracks.
  29381. *
  29382. * @param {Uint8Array} segment - The bytes of a single segment
  29383. * @param {Number} videoTrackId - The trackId of a video track in the segment
  29384. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  29385. * a list of seiNals found in that track
  29386. **/
  29387. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  29388. // To get the samples
  29389. var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
  29390. var mdats = probe.findBox(segment, ['mdat']);
  29391. var captionNals = {};
  29392. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  29393. mdats.forEach(function (mdat, index) {
  29394. var matchingTraf = trafs[index];
  29395. mdatTrafPairs.push({
  29396. mdat: mdat,
  29397. traf: matchingTraf
  29398. });
  29399. });
  29400. mdatTrafPairs.forEach(function (pair) {
  29401. var mdat = pair.mdat;
  29402. var traf = pair.traf;
  29403. var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
  29404. var headerInfo = mp4Inspector$$1.parseTfhd(tfhd[0]);
  29405. var trackId = headerInfo.trackId;
  29406. var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  29407. var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector$$1.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  29408. var truns = probe.findBox(traf, ['trun']);
  29409. var samples;
  29410. var seiNals; // Only parse video data for the chosen video track
  29411. if (videoTrackId === trackId && truns.length > 0) {
  29412. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  29413. seiNals = findSeiNals(mdat, samples, trackId);
  29414. if (!captionNals[trackId]) {
  29415. captionNals[trackId] = [];
  29416. }
  29417. captionNals[trackId] = captionNals[trackId].concat(seiNals);
  29418. }
  29419. });
  29420. return captionNals;
  29421. };
  29422. /**
  29423. * Parses out inband captions from an MP4 container and returns
  29424. * caption objects that can be used by WebVTT and the TextTrack API.
  29425. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  29426. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  29427. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  29428. *
  29429. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  29430. * @param {Number} trackId - The id of the video track to parse
  29431. * @param {Number} timescale - The timescale for the video track from the init segment
  29432. *
  29433. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  29434. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  29435. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  29436. * @return {String} parsedCaptions[].text - The visible content of the caption
  29437. **/
  29438. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  29439. var seiNals;
  29440. if (!trackId) {
  29441. return null;
  29442. }
  29443. seiNals = parseCaptionNals(segment, trackId);
  29444. return {
  29445. seiNals: seiNals[trackId],
  29446. timescale: timescale
  29447. };
  29448. };
  29449. /**
  29450. * Converts SEI NALUs into captions that can be used by video.js
  29451. **/
  29452. var CaptionParser$$1 = function CaptionParser$$1() {
  29453. var isInitialized = false;
  29454. var captionStream$$1; // Stores segments seen before trackId and timescale are set
  29455. var segmentCache; // Stores video track ID of the track being parsed
  29456. var trackId; // Stores the timescale of the track being parsed
  29457. var timescale; // Stores captions parsed so far
  29458. var parsedCaptions;
  29459. /**
  29460. * A method to indicate whether a CaptionParser has been initalized
  29461. * @returns {Boolean}
  29462. **/
  29463. this.isInitialized = function () {
  29464. return isInitialized;
  29465. };
  29466. /**
  29467. * Initializes the underlying CaptionStream, SEI NAL parsing
  29468. * and management, and caption collection
  29469. **/
  29470. this.init = function () {
  29471. captionStream$$1 = new CaptionStream$1();
  29472. isInitialized = true; // Collect dispatched captions
  29473. captionStream$$1.on('data', function (event) {
  29474. // Convert to seconds in the source's timescale
  29475. event.startTime = event.startPts / timescale;
  29476. event.endTime = event.endPts / timescale;
  29477. parsedCaptions.captions.push(event);
  29478. parsedCaptions.captionStreams[event.stream] = true;
  29479. });
  29480. };
  29481. /**
  29482. * Determines if a new video track will be selected
  29483. * or if the timescale changed
  29484. * @return {Boolean}
  29485. **/
  29486. this.isNewInit = function (videoTrackIds, timescales) {
  29487. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  29488. return false;
  29489. }
  29490. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  29491. };
  29492. /**
  29493. * Parses out SEI captions and interacts with underlying
  29494. * CaptionStream to return dispatched captions
  29495. *
  29496. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  29497. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  29498. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  29499. * @see parseEmbeddedCaptions
  29500. * @see m2ts/caption-stream.js
  29501. **/
  29502. this.parse = function (segment, videoTrackIds, timescales) {
  29503. var parsedData;
  29504. if (!this.isInitialized()) {
  29505. return null; // This is not likely to be a video segment
  29506. } else if (!videoTrackIds || !timescales) {
  29507. return null;
  29508. } else if (this.isNewInit(videoTrackIds, timescales)) {
  29509. // Use the first video track only as there is no
  29510. // mechanism to switch to other video tracks
  29511. trackId = videoTrackIds[0];
  29512. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  29513. // data until we have one
  29514. } else if (!trackId || !timescale) {
  29515. segmentCache.push(segment);
  29516. return null;
  29517. } // Now that a timescale and trackId is set, parse cached segments
  29518. while (segmentCache.length > 0) {
  29519. var cachedSegment = segmentCache.shift();
  29520. this.parse(cachedSegment, videoTrackIds, timescales);
  29521. }
  29522. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  29523. if (parsedData === null || !parsedData.seiNals) {
  29524. return null;
  29525. }
  29526. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  29527. this.flushStream();
  29528. return parsedCaptions;
  29529. };
  29530. /**
  29531. * Pushes SEI NALUs onto CaptionStream
  29532. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  29533. * Assumes that `parseCaptionNals` has been called first
  29534. * @see m2ts/caption-stream.js
  29535. **/
  29536. this.pushNals = function (nals) {
  29537. if (!this.isInitialized() || !nals || nals.length === 0) {
  29538. return null;
  29539. }
  29540. nals.forEach(function (nal) {
  29541. captionStream$$1.push(nal);
  29542. });
  29543. };
  29544. /**
  29545. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  29546. * @see m2ts/caption-stream.js
  29547. **/
  29548. this.flushStream = function () {
  29549. if (!this.isInitialized()) {
  29550. return null;
  29551. }
  29552. captionStream$$1.flush();
  29553. };
  29554. /**
  29555. * Reset caption buckets for new data
  29556. **/
  29557. this.clearParsedCaptions = function () {
  29558. parsedCaptions.captions = [];
  29559. parsedCaptions.captionStreams = {};
  29560. };
  29561. /**
  29562. * Resets underlying CaptionStream
  29563. * @see m2ts/caption-stream.js
  29564. **/
  29565. this.resetCaptionStream = function () {
  29566. if (!this.isInitialized()) {
  29567. return null;
  29568. }
  29569. captionStream$$1.reset();
  29570. };
  29571. /**
  29572. * Convenience method to clear all captions flushed from the
  29573. * CaptionStream and still being parsed
  29574. * @see m2ts/caption-stream.js
  29575. **/
  29576. this.clearAllCaptions = function () {
  29577. this.clearParsedCaptions();
  29578. this.resetCaptionStream();
  29579. };
  29580. /**
  29581. * Reset caption parser
  29582. **/
  29583. this.reset = function () {
  29584. segmentCache = [];
  29585. trackId = null;
  29586. timescale = null;
  29587. if (!parsedCaptions) {
  29588. parsedCaptions = {
  29589. captions: [],
  29590. // CC1, CC2, CC3, CC4
  29591. captionStreams: {}
  29592. };
  29593. } else {
  29594. this.clearParsedCaptions();
  29595. }
  29596. this.resetCaptionStream();
  29597. };
  29598. this.reset();
  29599. };
  29600. var captionParser = CaptionParser$$1;
  29601. var mp4$$1 = {
  29602. generator: mp4Generator,
  29603. probe: probe,
  29604. Transmuxer: transmuxer.Transmuxer,
  29605. AudioSegmentStream: transmuxer.AudioSegmentStream,
  29606. VideoSegmentStream: transmuxer.VideoSegmentStream,
  29607. CaptionParser: captionParser
  29608. };
  29609. var classCallCheck = function classCallCheck(instance, Constructor) {
  29610. if (!(instance instanceof Constructor)) {
  29611. throw new TypeError("Cannot call a class as a function");
  29612. }
  29613. };
  29614. var createClass = function () {
  29615. function defineProperties(target, props) {
  29616. for (var i = 0; i < props.length; i++) {
  29617. var descriptor = props[i];
  29618. descriptor.enumerable = descriptor.enumerable || false;
  29619. descriptor.configurable = true;
  29620. if ("value" in descriptor) descriptor.writable = true;
  29621. Object.defineProperty(target, descriptor.key, descriptor);
  29622. }
  29623. }
  29624. return function (Constructor, protoProps, staticProps) {
  29625. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  29626. if (staticProps) defineProperties(Constructor, staticProps);
  29627. return Constructor;
  29628. };
  29629. }();
  29630. /**
  29631. * @file transmuxer-worker.js
  29632. */
  29633. /**
  29634. * Re-emits transmuxer events by converting them into messages to the
  29635. * world outside the worker.
  29636. *
  29637. * @param {Object} transmuxer the transmuxer to wire events on
  29638. * @private
  29639. */
  29640. var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) {
  29641. transmuxer.on('data', function (segment) {
  29642. // transfer ownership of the underlying ArrayBuffer
  29643. // instead of doing a copy to save memory
  29644. // ArrayBuffers are transferable but generic TypedArrays are not
  29645. // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
  29646. var initArray = segment.initSegment;
  29647. segment.initSegment = {
  29648. data: initArray.buffer,
  29649. byteOffset: initArray.byteOffset,
  29650. byteLength: initArray.byteLength
  29651. };
  29652. var typedArray = segment.data;
  29653. segment.data = typedArray.buffer;
  29654. self.postMessage({
  29655. action: 'data',
  29656. segment: segment,
  29657. byteOffset: typedArray.byteOffset,
  29658. byteLength: typedArray.byteLength
  29659. }, [segment.data]);
  29660. });
  29661. if (transmuxer.captionStream) {
  29662. transmuxer.captionStream.on('data', function (caption) {
  29663. self.postMessage({
  29664. action: 'caption',
  29665. data: caption
  29666. });
  29667. });
  29668. }
  29669. transmuxer.on('done', function (data) {
  29670. self.postMessage({
  29671. action: 'done'
  29672. });
  29673. });
  29674. transmuxer.on('gopInfo', function (gopInfo) {
  29675. self.postMessage({
  29676. action: 'gopInfo',
  29677. gopInfo: gopInfo
  29678. });
  29679. });
  29680. transmuxer.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) {
  29681. self.postMessage({
  29682. action: 'videoSegmentTimingInfo',
  29683. videoSegmentTimingInfo: videoSegmentTimingInfo
  29684. });
  29685. });
  29686. };
  29687. /**
  29688. * All incoming messages route through this hash. If no function exists
  29689. * to handle an incoming message, then we ignore the message.
  29690. *
  29691. * @class MessageHandlers
  29692. * @param {Object} options the options to initialize with
  29693. */
  29694. var MessageHandlers = function () {
  29695. function MessageHandlers(self, options) {
  29696. classCallCheck(this, MessageHandlers);
  29697. this.options = options || {};
  29698. this.self = self;
  29699. this.init();
  29700. }
  29701. /**
  29702. * initialize our web worker and wire all the events.
  29703. */
  29704. createClass(MessageHandlers, [{
  29705. key: 'init',
  29706. value: function init() {
  29707. if (this.transmuxer) {
  29708. this.transmuxer.dispose();
  29709. }
  29710. this.transmuxer = new mp4$$1.Transmuxer(this.options);
  29711. wireTransmuxerEvents(this.self, this.transmuxer);
  29712. }
  29713. /**
  29714. * Adds data (a ts segment) to the start of the transmuxer pipeline for
  29715. * processing.
  29716. *
  29717. * @param {ArrayBuffer} data data to push into the muxer
  29718. */
  29719. }, {
  29720. key: 'push',
  29721. value: function push(data) {
  29722. // Cast array buffer to correct type for transmuxer
  29723. var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
  29724. this.transmuxer.push(segment);
  29725. }
  29726. /**
  29727. * Recreate the transmuxer so that the next segment added via `push`
  29728. * start with a fresh transmuxer.
  29729. */
  29730. }, {
  29731. key: 'reset',
  29732. value: function reset() {
  29733. this.init();
  29734. }
  29735. /**
  29736. * Set the value that will be used as the `baseMediaDecodeTime` time for the
  29737. * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
  29738. * set relative to the first based on the PTS values.
  29739. *
  29740. * @param {Object} data used to set the timestamp offset in the muxer
  29741. */
  29742. }, {
  29743. key: 'setTimestampOffset',
  29744. value: function setTimestampOffset(data) {
  29745. var timestampOffset = data.timestampOffset || 0;
  29746. this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
  29747. }
  29748. }, {
  29749. key: 'setAudioAppendStart',
  29750. value: function setAudioAppendStart(data) {
  29751. this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
  29752. }
  29753. /**
  29754. * Forces the pipeline to finish processing the last segment and emit it's
  29755. * results.
  29756. *
  29757. * @param {Object} data event data, not really used
  29758. */
  29759. }, {
  29760. key: 'flush',
  29761. value: function flush(data) {
  29762. this.transmuxer.flush();
  29763. }
  29764. }, {
  29765. key: 'resetCaptions',
  29766. value: function resetCaptions() {
  29767. this.transmuxer.resetCaptions();
  29768. }
  29769. }, {
  29770. key: 'alignGopsWith',
  29771. value: function alignGopsWith(data) {
  29772. this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
  29773. }
  29774. }]);
  29775. return MessageHandlers;
  29776. }();
  29777. /**
  29778. * Our web wroker interface so that things can talk to mux.js
  29779. * that will be running in a web worker. the scope is passed to this by
  29780. * webworkify.
  29781. *
  29782. * @param {Object} self the scope for the web worker
  29783. */
  29784. var TransmuxerWorker = function TransmuxerWorker(self) {
  29785. self.onmessage = function (event) {
  29786. if (event.data.action === 'init' && event.data.options) {
  29787. this.messageHandlers = new MessageHandlers(self, event.data.options);
  29788. return;
  29789. }
  29790. if (!this.messageHandlers) {
  29791. this.messageHandlers = new MessageHandlers(self);
  29792. }
  29793. if (event.data && event.data.action && event.data.action !== 'init') {
  29794. if (this.messageHandlers[event.data.action]) {
  29795. this.messageHandlers[event.data.action](event.data);
  29796. }
  29797. }
  29798. };
  29799. };
  29800. var transmuxerWorker = new TransmuxerWorker(self);
  29801. return transmuxerWorker;
  29802. }();
  29803. });
  29804. /**
  29805. * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
  29806. * codec strings, or translating codec strings into objects that can be examined.
  29807. */
  29808. // Default codec parameters if none were provided for video and/or audio
  29809. var defaultCodecs = {
  29810. videoCodec: 'avc1',
  29811. videoObjectTypeIndicator: '.4d400d',
  29812. // AAC-LC
  29813. audioProfile: '2'
  29814. };
  29815. /**
  29816. * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
  29817. * `avc1.<hhhhhh>`
  29818. *
  29819. * @param {Array} codecs an array of codec strings to fix
  29820. * @return {Array} the translated codec array
  29821. * @private
  29822. */
  29823. var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
  29824. return codecs.map(function (codec) {
  29825. return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
  29826. var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
  29827. var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
  29828. return 'avc1.' + profileHex + '00' + avcLevelHex;
  29829. });
  29830. });
  29831. };
  29832. /**
  29833. * Parses a codec string to retrieve the number of codecs specified,
  29834. * the video codec and object type indicator, and the audio profile.
  29835. */
  29836. var parseCodecs = function parseCodecs() {
  29837. var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  29838. var result = {
  29839. codecCount: 0
  29840. };
  29841. var parsed = void 0;
  29842. result.codecCount = codecs.split(',').length;
  29843. result.codecCount = result.codecCount || 2; // parse the video codec
  29844. parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
  29845. if (parsed) {
  29846. result.videoCodec = parsed[2];
  29847. result.videoObjectTypeIndicator = parsed[3];
  29848. } // parse the last field of the audio codec
  29849. result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
  29850. result.audioProfile = result.audioProfile && result.audioProfile[2];
  29851. return result;
  29852. };
  29853. /**
  29854. * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
  29855. * standard `avc1.<hhhhhh>`.
  29856. *
  29857. * @param codecString {String} the codec string
  29858. * @return {String} the codec string with old apple-style codecs replaced
  29859. *
  29860. * @private
  29861. */
  29862. var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
  29863. return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
  29864. return translateLegacyCodecs([match])[0];
  29865. });
  29866. };
  29867. /**
  29868. * Build a media mime-type string from a set of parameters
  29869. * @param {String} type either 'audio' or 'video'
  29870. * @param {String} container either 'mp2t' or 'mp4'
  29871. * @param {Array} codecs an array of codec strings to add
  29872. * @return {String} a valid media mime-type
  29873. */
  29874. var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
  29875. // The codecs array is filtered so that falsey values are
  29876. // dropped and don't cause Array#join to create spurious
  29877. // commas
  29878. return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
  29879. return !!c;
  29880. }).join(', ') + '"';
  29881. };
  29882. /**
  29883. * Returns the type container based on information in the playlist
  29884. * @param {Playlist} media the current media playlist
  29885. * @return {String} a valid media container type
  29886. */
  29887. var getContainerType = function getContainerType(media) {
  29888. // An initialization segment means the media playlist is an iframe
  29889. // playlist or is using the mp4 container. We don't currently
  29890. // support iframe playlists, so assume this is signalling mp4
  29891. // fragments.
  29892. if (media.segments && media.segments.length && media.segments[0].map) {
  29893. return 'mp4';
  29894. }
  29895. return 'mp2t';
  29896. };
  29897. /**
  29898. * Returns a set of codec strings parsed from the playlist or the default
  29899. * codec strings if no codecs were specified in the playlist
  29900. * @param {Playlist} media the current media playlist
  29901. * @return {Object} an object with the video and audio codecs
  29902. */
  29903. var getCodecs = function getCodecs(media) {
  29904. // if the codecs were explicitly specified, use them instead of the
  29905. // defaults
  29906. var mediaAttributes = media.attributes || {};
  29907. if (mediaAttributes.CODECS) {
  29908. return parseCodecs(mediaAttributes.CODECS);
  29909. }
  29910. return defaultCodecs;
  29911. };
  29912. var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
  29913. if (!master.mediaGroups.AUDIO || !audioGroupId) {
  29914. return null;
  29915. }
  29916. var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
  29917. if (!audioGroup) {
  29918. return null;
  29919. }
  29920. for (var name in audioGroup) {
  29921. var audioType = audioGroup[name];
  29922. if (audioType.default && audioType.playlists) {
  29923. // codec should be the same for all playlists within the audio type
  29924. return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
  29925. }
  29926. }
  29927. return null;
  29928. };
  29929. /**
  29930. * Calculates the MIME type strings for a working configuration of
  29931. * SourceBuffers to play variant streams in a master playlist. If
  29932. * there is no possible working configuration, an empty array will be
  29933. * returned.
  29934. *
  29935. * @param master {Object} the m3u8 object for the master playlist
  29936. * @param media {Object} the m3u8 object for the variant playlist
  29937. * @return {Array} the MIME type strings. If the array has more than
  29938. * one entry, the first element should be applied to the video
  29939. * SourceBuffer and the second to the audio SourceBuffer.
  29940. *
  29941. * @private
  29942. */
  29943. var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
  29944. var containerType = getContainerType(media);
  29945. var codecInfo = getCodecs(media);
  29946. var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video)
  29947. var isMuxed = true;
  29948. var isMaat = false;
  29949. if (!media) {
  29950. // Not enough information
  29951. return [];
  29952. }
  29953. if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
  29954. var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario
  29955. if (audioGroup) {
  29956. isMaat = true; // Start with the everything demuxed then...
  29957. isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
  29958. for (var groupId in audioGroup) {
  29959. // either a uri is present (if the case of HLS and an external playlist), or
  29960. // playlists is present (in the case of DASH where we don't have external audio
  29961. // playlists)
  29962. if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
  29963. isMuxed = true;
  29964. break;
  29965. }
  29966. }
  29967. }
  29968. } // HLS with multiple-audio tracks must always get an audio codec.
  29969. // Put another way, there is no way to have a video-only multiple-audio HLS!
  29970. if (isMaat && !codecInfo.audioProfile) {
  29971. if (!isMuxed) {
  29972. // It is possible for codecs to be specified on the audio media group playlist but
  29973. // not on the rendition playlist. This is mostly the case for DASH, where audio and
  29974. // video are always separate (and separately specified).
  29975. codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
  29976. }
  29977. if (!codecInfo.audioProfile) {
  29978. 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)');
  29979. codecInfo.audioProfile = defaultCodecs.audioProfile;
  29980. }
  29981. } // Generate the final codec strings from the codec object generated above
  29982. var codecStrings = {};
  29983. if (codecInfo.videoCodec) {
  29984. codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
  29985. }
  29986. if (codecInfo.audioProfile) {
  29987. codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
  29988. } // Finally, make and return an array with proper mime-types depending on
  29989. // the configuration
  29990. var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
  29991. var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
  29992. var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
  29993. if (isMaat) {
  29994. if (!isMuxed && codecStrings.video) {
  29995. return [justVideo, justAudio];
  29996. }
  29997. if (!isMuxed && !codecStrings.video) {
  29998. // There is no muxed content and no video codec string, so this is an audio only
  29999. // stream with alternate audio.
  30000. return [justAudio, justAudio];
  30001. } // There exists the possiblity that this will return a `video/container`
  30002. // mime-type for the first entry in the array even when there is only audio.
  30003. // This doesn't appear to be a problem and simplifies the code.
  30004. return [bothVideoAudio, justAudio];
  30005. } // If there is no video codec at all, always just return a single
  30006. // audio/<container> mime-type
  30007. if (!codecStrings.video) {
  30008. return [justAudio];
  30009. } // When not using separate audio media groups, audio and video is
  30010. // *always* muxed
  30011. return [bothVideoAudio];
  30012. };
  30013. /**
  30014. * Parse a content type header into a type and parameters
  30015. * object
  30016. *
  30017. * @param {String} type the content type header
  30018. * @return {Object} the parsed content-type
  30019. * @private
  30020. */
  30021. var parseContentType = function parseContentType(type) {
  30022. var object = {
  30023. type: '',
  30024. parameters: {}
  30025. };
  30026. var parameters = type.trim().split(';'); // first parameter should always be content-type
  30027. object.type = parameters.shift().trim();
  30028. parameters.forEach(function (parameter) {
  30029. var pair = parameter.trim().split('=');
  30030. if (pair.length > 1) {
  30031. var name = pair[0].replace(/"/g, '').trim();
  30032. var value = pair[1].replace(/"/g, '').trim();
  30033. object.parameters[name] = value;
  30034. }
  30035. });
  30036. return object;
  30037. };
  30038. /**
  30039. * Check if a codec string refers to an audio codec.
  30040. *
  30041. * @param {String} codec codec string to check
  30042. * @return {Boolean} if this is an audio codec
  30043. * @private
  30044. */
  30045. var isAudioCodec = function isAudioCodec(codec) {
  30046. return /mp4a\.\d+.\d+/i.test(codec);
  30047. };
  30048. /**
  30049. * Check if a codec string refers to a video codec.
  30050. *
  30051. * @param {String} codec codec string to check
  30052. * @return {Boolean} if this is a video codec
  30053. * @private
  30054. */
  30055. var isVideoCodec = function isVideoCodec(codec) {
  30056. return /avc1\.[\da-f]+/i.test(codec);
  30057. };
  30058. /**
  30059. * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
  30060. * front of current time.
  30061. *
  30062. * @param {Array} buffer
  30063. * The current buffer of gop information
  30064. * @param {Number} currentTime
  30065. * The current time
  30066. * @param {Double} mapping
  30067. * Offset to map display time to stream presentation time
  30068. * @return {Array}
  30069. * List of gops considered safe to append over
  30070. */
  30071. var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
  30072. if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
  30073. return [];
  30074. } // pts value for current time + 3 seconds to give a bit more wiggle room
  30075. var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000);
  30076. var i = void 0;
  30077. for (i = 0; i < buffer.length; i++) {
  30078. if (buffer[i].pts > currentTimePts) {
  30079. break;
  30080. }
  30081. }
  30082. return buffer.slice(i);
  30083. };
  30084. /**
  30085. * Appends gop information (timing and byteLength) received by the transmuxer for the
  30086. * gops appended in the last call to appendBuffer
  30087. *
  30088. * @param {Array} buffer
  30089. * The current buffer of gop information
  30090. * @param {Array} gops
  30091. * List of new gop information
  30092. * @param {boolean} replace
  30093. * If true, replace the buffer with the new gop information. If false, append the
  30094. * new gop information to the buffer in the right location of time.
  30095. * @return {Array}
  30096. * Updated list of gop information
  30097. */
  30098. var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
  30099. if (!gops.length) {
  30100. return buffer;
  30101. }
  30102. if (replace) {
  30103. // If we are in safe append mode, then completely overwrite the gop buffer
  30104. // with the most recent appeneded data. This will make sure that when appending
  30105. // future segments, we only try to align with gops that are both ahead of current
  30106. // time and in the last segment appended.
  30107. return gops.slice();
  30108. }
  30109. var start = gops[0].pts;
  30110. var i = 0;
  30111. for (i; i < buffer.length; i++) {
  30112. if (buffer[i].pts >= start) {
  30113. break;
  30114. }
  30115. }
  30116. return buffer.slice(0, i).concat(gops);
  30117. };
  30118. /**
  30119. * Removes gop information in buffer that overlaps with provided start and end
  30120. *
  30121. * @param {Array} buffer
  30122. * The current buffer of gop information
  30123. * @param {Double} start
  30124. * position to start the remove at
  30125. * @param {Double} end
  30126. * position to end the remove at
  30127. * @param {Double} mapping
  30128. * Offset to map display time to stream presentation time
  30129. */
  30130. var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
  30131. var startPts = Math.ceil((start - mapping) * 90000);
  30132. var endPts = Math.ceil((end - mapping) * 90000);
  30133. var updatedBuffer = buffer.slice();
  30134. var i = buffer.length;
  30135. while (i--) {
  30136. if (buffer[i].pts <= endPts) {
  30137. break;
  30138. }
  30139. }
  30140. if (i === -1) {
  30141. // no removal because end of remove range is before start of buffer
  30142. return updatedBuffer;
  30143. }
  30144. var j = i + 1;
  30145. while (j--) {
  30146. if (buffer[j].pts <= startPts) {
  30147. break;
  30148. }
  30149. } // clamp remove range start to 0 index
  30150. j = Math.max(j, 0);
  30151. updatedBuffer.splice(j, i - j + 1);
  30152. return updatedBuffer;
  30153. };
  30154. var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) {
  30155. var start = null;
  30156. var end = null;
  30157. var arity = 0;
  30158. var extents = [];
  30159. var ranges = []; // neither buffer has been created yet
  30160. if (!videoBuffer && !audioBuffer) {
  30161. return videojs$1.createTimeRange();
  30162. } // only one buffer is configured
  30163. if (!videoBuffer) {
  30164. return audioBuffer.buffered;
  30165. }
  30166. if (!audioBuffer) {
  30167. return videoBuffer.buffered;
  30168. } // both buffers are configured
  30169. if (audioDisabled) {
  30170. return videoBuffer.buffered;
  30171. } // both buffers are empty
  30172. if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) {
  30173. return videojs$1.createTimeRange();
  30174. } // Handle the case where we have both buffers and create an
  30175. // intersection of the two
  30176. var videoBuffered = videoBuffer.buffered;
  30177. var audioBuffered = audioBuffer.buffered;
  30178. var count = videoBuffered.length; // A) Gather up all start and end times
  30179. while (count--) {
  30180. extents.push({
  30181. time: videoBuffered.start(count),
  30182. type: 'start'
  30183. });
  30184. extents.push({
  30185. time: videoBuffered.end(count),
  30186. type: 'end'
  30187. });
  30188. }
  30189. count = audioBuffered.length;
  30190. while (count--) {
  30191. extents.push({
  30192. time: audioBuffered.start(count),
  30193. type: 'start'
  30194. });
  30195. extents.push({
  30196. time: audioBuffered.end(count),
  30197. type: 'end'
  30198. });
  30199. } // B) Sort them by time
  30200. extents.sort(function (a, b) {
  30201. return a.time - b.time;
  30202. }); // C) Go along one by one incrementing arity for start and decrementing
  30203. // arity for ends
  30204. for (count = 0; count < extents.length; count++) {
  30205. if (extents[count].type === 'start') {
  30206. arity++; // D) If arity is ever incremented to 2 we are entering an
  30207. // overlapping range
  30208. if (arity === 2) {
  30209. start = extents[count].time;
  30210. }
  30211. } else if (extents[count].type === 'end') {
  30212. arity--; // E) If arity is ever decremented to 1 we leaving an
  30213. // overlapping range
  30214. if (arity === 1) {
  30215. end = extents[count].time;
  30216. }
  30217. } // F) Record overlapping ranges
  30218. if (start !== null && end !== null) {
  30219. ranges.push([start, end]);
  30220. start = null;
  30221. end = null;
  30222. }
  30223. }
  30224. return videojs$1.createTimeRanges(ranges);
  30225. };
  30226. /**
  30227. * @file virtual-source-buffer.js
  30228. */
  30229. var ONE_SECOND_IN_TS = 90000; // We create a wrapper around the SourceBuffer so that we can manage the
  30230. // state of the `updating` property manually. We have to do this because
  30231. // Firefox changes `updating` to false long before triggering `updateend`
  30232. // events and that was causing strange problems in videojs-contrib-hls
  30233. var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
  30234. var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
  30235. var wrapper = Object.create(null);
  30236. wrapper.updating = false;
  30237. wrapper.realBuffer_ = sourceBuffer;
  30238. var _loop = function _loop(key) {
  30239. if (typeof sourceBuffer[key] === 'function') {
  30240. wrapper[key] = function () {
  30241. return sourceBuffer[key].apply(sourceBuffer, arguments);
  30242. };
  30243. } else if (typeof wrapper[key] === 'undefined') {
  30244. Object.defineProperty(wrapper, key, {
  30245. get: function get$$1() {
  30246. return sourceBuffer[key];
  30247. },
  30248. set: function set$$1(v) {
  30249. return sourceBuffer[key] = v;
  30250. }
  30251. });
  30252. }
  30253. };
  30254. for (var key in sourceBuffer) {
  30255. _loop(key);
  30256. }
  30257. return wrapper;
  30258. };
  30259. /**
  30260. * VirtualSourceBuffers exist so that we can transmux non native formats
  30261. * into a native format, but keep the same api as a native source buffer.
  30262. * It creates a transmuxer, that works in its own thread (a web worker) and
  30263. * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
  30264. * then send all of that data to the naive sourcebuffer so that it is
  30265. * indestinguishable from a natively supported format.
  30266. *
  30267. * @param {HtmlMediaSource} mediaSource the parent mediaSource
  30268. * @param {Array} codecs array of codecs that we will be dealing with
  30269. * @class VirtualSourceBuffer
  30270. * @extends video.js.EventTarget
  30271. */
  30272. var VirtualSourceBuffer = function (_videojs$EventTarget) {
  30273. inherits(VirtualSourceBuffer, _videojs$EventTarget);
  30274. function VirtualSourceBuffer(mediaSource, codecs) {
  30275. classCallCheck(this, VirtualSourceBuffer);
  30276. var _this = possibleConstructorReturn(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
  30277. _this.timestampOffset_ = 0;
  30278. _this.pendingBuffers_ = [];
  30279. _this.bufferUpdating_ = false;
  30280. _this.mediaSource_ = mediaSource;
  30281. _this.codecs_ = codecs;
  30282. _this.audioCodec_ = null;
  30283. _this.videoCodec_ = null;
  30284. _this.audioDisabled_ = false;
  30285. _this.appendAudioInitSegment_ = true;
  30286. _this.gopBuffer_ = [];
  30287. _this.timeMapping_ = 0;
  30288. _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
  30289. var options = {
  30290. remux: false,
  30291. alignGopsAtEnd: _this.safeAppend_
  30292. };
  30293. _this.codecs_.forEach(function (codec) {
  30294. if (isAudioCodec(codec)) {
  30295. _this.audioCodec_ = codec;
  30296. } else if (isVideoCodec(codec)) {
  30297. _this.videoCodec_ = codec;
  30298. }
  30299. }); // append muxed segments to their respective native buffers as
  30300. // soon as they are available
  30301. _this.transmuxer_ = new TransmuxWorker();
  30302. _this.transmuxer_.postMessage({
  30303. action: 'init',
  30304. options: options
  30305. });
  30306. _this.transmuxer_.onmessage = function (event) {
  30307. if (event.data.action === 'data') {
  30308. return _this.data_(event);
  30309. }
  30310. if (event.data.action === 'done') {
  30311. return _this.done_(event);
  30312. }
  30313. if (event.data.action === 'gopInfo') {
  30314. return _this.appendGopInfo_(event);
  30315. }
  30316. if (event.data.action === 'videoSegmentTimingInfo') {
  30317. return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo);
  30318. }
  30319. }; // this timestampOffset is a property with the side-effect of resetting
  30320. // baseMediaDecodeTime in the transmuxer on the setter
  30321. Object.defineProperty(_this, 'timestampOffset', {
  30322. get: function get$$1() {
  30323. return this.timestampOffset_;
  30324. },
  30325. set: function set$$1(val) {
  30326. if (typeof val === 'number' && val >= 0) {
  30327. this.timestampOffset_ = val;
  30328. this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline
  30329. this.gopBuffer_.length = 0;
  30330. this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to
  30331. // the desired timestampOffset for the next segment
  30332. this.transmuxer_.postMessage({
  30333. action: 'setTimestampOffset',
  30334. timestampOffset: val
  30335. });
  30336. }
  30337. }
  30338. }); // setting the append window affects both source buffers
  30339. Object.defineProperty(_this, 'appendWindowStart', {
  30340. get: function get$$1() {
  30341. return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
  30342. },
  30343. set: function set$$1(start) {
  30344. if (this.videoBuffer_) {
  30345. this.videoBuffer_.appendWindowStart = start;
  30346. }
  30347. if (this.audioBuffer_) {
  30348. this.audioBuffer_.appendWindowStart = start;
  30349. }
  30350. }
  30351. }); // this buffer is "updating" if either of its native buffers are
  30352. Object.defineProperty(_this, 'updating', {
  30353. get: function get$$1() {
  30354. return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
  30355. }
  30356. }); // the buffered property is the intersection of the buffered
  30357. // ranges of the native source buffers
  30358. Object.defineProperty(_this, 'buffered', {
  30359. get: function get$$1() {
  30360. return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_);
  30361. }
  30362. });
  30363. return _this;
  30364. }
  30365. /**
  30366. * When we get a data event from the transmuxer
  30367. * we call this function and handle the data that
  30368. * was sent to us
  30369. *
  30370. * @private
  30371. * @param {Event} event the data event from the transmuxer
  30372. */
  30373. createClass(VirtualSourceBuffer, [{
  30374. key: 'data_',
  30375. value: function data_(event) {
  30376. var segment = event.data.segment; // Cast ArrayBuffer to TypedArray
  30377. segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
  30378. segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
  30379. createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array
  30380. this.pendingBuffers_.push(segment);
  30381. return;
  30382. }
  30383. /**
  30384. * When we get a done event from the transmuxer
  30385. * we call this function and we process all
  30386. * of the pending data that we have been saving in the
  30387. * data_ function
  30388. *
  30389. * @private
  30390. * @param {Event} event the done event from the transmuxer
  30391. */
  30392. }, {
  30393. key: 'done_',
  30394. value: function done_(event) {
  30395. // Don't process and append data if the mediaSource is closed
  30396. if (this.mediaSource_.readyState === 'closed') {
  30397. this.pendingBuffers_.length = 0;
  30398. return;
  30399. } // All buffers should have been flushed from the muxer
  30400. // start processing anything we have received
  30401. this.processPendingSegments_();
  30402. return;
  30403. }
  30404. }, {
  30405. key: 'videoSegmentTimingInfo_',
  30406. value: function videoSegmentTimingInfo_(timingInfo) {
  30407. var timingInfoInSeconds = {
  30408. start: {
  30409. decode: timingInfo.start.dts / ONE_SECOND_IN_TS,
  30410. presentation: timingInfo.start.pts / ONE_SECOND_IN_TS
  30411. },
  30412. end: {
  30413. decode: timingInfo.end.dts / ONE_SECOND_IN_TS,
  30414. presentation: timingInfo.end.pts / ONE_SECOND_IN_TS
  30415. },
  30416. baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS
  30417. };
  30418. if (timingInfo.prependedContentDuration) {
  30419. timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS;
  30420. }
  30421. this.trigger({
  30422. type: 'videoSegmentTimingInfo',
  30423. videoSegmentTimingInfo: timingInfoInSeconds
  30424. });
  30425. }
  30426. /**
  30427. * Create our internal native audio/video source buffers and add
  30428. * event handlers to them with the following conditions:
  30429. * 1. they do not already exist on the mediaSource
  30430. * 2. this VSB has a codec for them
  30431. *
  30432. * @private
  30433. */
  30434. }, {
  30435. key: 'createRealSourceBuffers_',
  30436. value: function createRealSourceBuffers_() {
  30437. var _this2 = this;
  30438. var types = ['audio', 'video'];
  30439. types.forEach(function (type) {
  30440. // Don't create a SourceBuffer of this type if we don't have a
  30441. // codec for it
  30442. if (!_this2[type + 'Codec_']) {
  30443. return;
  30444. } // Do nothing if a SourceBuffer of this type already exists
  30445. if (_this2[type + 'Buffer_']) {
  30446. return;
  30447. }
  30448. var buffer = null; // If the mediasource already has a SourceBuffer for the codec
  30449. // use that
  30450. if (_this2.mediaSource_[type + 'Buffer_']) {
  30451. buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled
  30452. // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
  30453. // than createRealSourceBuffers_ is called to create the second
  30454. // VirtualSourceBuffer because that happens as a side-effect of
  30455. // videojs-contrib-hls starting the audioSegmentLoader. As a result,
  30456. // the audioBuffer is essentially "ownerless" and no one will toggle
  30457. // the `updating` state back to false once the `updateend` event is received
  30458. //
  30459. // Setting `updating` to false manually will work around this
  30460. // situation and allow work to continue
  30461. buffer.updating = false;
  30462. } else {
  30463. var codecProperty = type + 'Codec_';
  30464. var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
  30465. buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
  30466. _this2.mediaSource_[type + 'Buffer_'] = buffer;
  30467. }
  30468. _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer
  30469. ['update', 'updatestart', 'updateend'].forEach(function (event) {
  30470. buffer.addEventListener(event, function () {
  30471. // if audio is disabled
  30472. if (type === 'audio' && _this2.audioDisabled_) {
  30473. return;
  30474. }
  30475. if (event === 'updateend') {
  30476. _this2[type + 'Buffer_'].updating = false;
  30477. }
  30478. var shouldTrigger = types.every(function (t) {
  30479. // skip checking audio's updating status if audio
  30480. // is not enabled
  30481. if (t === 'audio' && _this2.audioDisabled_) {
  30482. return true;
  30483. } // if the other type is updating we don't trigger
  30484. if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
  30485. return false;
  30486. }
  30487. return true;
  30488. });
  30489. if (shouldTrigger) {
  30490. return _this2.trigger(event);
  30491. }
  30492. });
  30493. });
  30494. });
  30495. }
  30496. /**
  30497. * Emulate the native mediasource function, but our function will
  30498. * send all of the proposed segments to the transmuxer so that we
  30499. * can transmux them before we append them to our internal
  30500. * native source buffers in the correct format.
  30501. *
  30502. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
  30503. * @param {Uint8Array} segment the segment to append to the buffer
  30504. */
  30505. }, {
  30506. key: 'appendBuffer',
  30507. value: function appendBuffer(segment) {
  30508. // Start the internal "updating" state
  30509. this.bufferUpdating_ = true;
  30510. if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
  30511. var audioBuffered = this.audioBuffer_.buffered;
  30512. this.transmuxer_.postMessage({
  30513. action: 'setAudioAppendStart',
  30514. appendStart: audioBuffered.end(audioBuffered.length - 1)
  30515. });
  30516. }
  30517. if (this.videoBuffer_) {
  30518. this.transmuxer_.postMessage({
  30519. action: 'alignGopsWith',
  30520. gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_)
  30521. });
  30522. }
  30523. this.transmuxer_.postMessage({
  30524. action: 'push',
  30525. // Send the typed-array of data as an ArrayBuffer so that
  30526. // it can be sent as a "Transferable" and avoid the costly
  30527. // memory copy
  30528. data: segment.buffer,
  30529. // To recreate the original typed-array, we need information
  30530. // about what portion of the ArrayBuffer it was a view into
  30531. byteOffset: segment.byteOffset,
  30532. byteLength: segment.byteLength
  30533. }, [segment.buffer]);
  30534. this.transmuxer_.postMessage({
  30535. action: 'flush'
  30536. });
  30537. }
  30538. /**
  30539. * Appends gop information (timing and byteLength) received by the transmuxer for the
  30540. * gops appended in the last call to appendBuffer
  30541. *
  30542. * @param {Event} event
  30543. * The gopInfo event from the transmuxer
  30544. * @param {Array} event.data.gopInfo
  30545. * List of gop info to append
  30546. */
  30547. }, {
  30548. key: 'appendGopInfo_',
  30549. value: function appendGopInfo_(event) {
  30550. this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
  30551. }
  30552. /**
  30553. * Emulate the native mediasource function and remove parts
  30554. * of the buffer from any of our internal buffers that exist
  30555. *
  30556. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
  30557. * @param {Double} start position to start the remove at
  30558. * @param {Double} end position to end the remove at
  30559. */
  30560. }, {
  30561. key: 'remove',
  30562. value: function remove(start, end) {
  30563. if (this.videoBuffer_) {
  30564. this.videoBuffer_.updating = true;
  30565. this.videoBuffer_.remove(start, end);
  30566. this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
  30567. }
  30568. if (!this.audioDisabled_ && this.audioBuffer_) {
  30569. this.audioBuffer_.updating = true;
  30570. this.audioBuffer_.remove(start, end);
  30571. } // Remove Metadata Cues (id3)
  30572. removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions
  30573. if (this.inbandTextTracks_) {
  30574. for (var track in this.inbandTextTracks_) {
  30575. removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
  30576. }
  30577. }
  30578. }
  30579. /**
  30580. * Process any segments that the muxer has output
  30581. * Concatenate segments together based on type and append them into
  30582. * their respective sourceBuffers
  30583. *
  30584. * @private
  30585. */
  30586. }, {
  30587. key: 'processPendingSegments_',
  30588. value: function processPendingSegments_() {
  30589. var sortedSegments = {
  30590. video: {
  30591. segments: [],
  30592. bytes: 0
  30593. },
  30594. audio: {
  30595. segments: [],
  30596. bytes: 0
  30597. },
  30598. captions: [],
  30599. metadata: []
  30600. }; // Sort segments into separate video/audio arrays and
  30601. // keep track of their total byte lengths
  30602. sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
  30603. var type = segment.type;
  30604. var data = segment.data;
  30605. var initSegment = segment.initSegment;
  30606. segmentObj[type].segments.push(data);
  30607. segmentObj[type].bytes += data.byteLength;
  30608. segmentObj[type].initSegment = initSegment; // Gather any captions into a single array
  30609. if (segment.captions) {
  30610. segmentObj.captions = segmentObj.captions.concat(segment.captions);
  30611. }
  30612. if (segment.info) {
  30613. segmentObj[type].info = segment.info;
  30614. } // Gather any metadata into a single array
  30615. if (segment.metadata) {
  30616. segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
  30617. }
  30618. return segmentObj;
  30619. }, sortedSegments); // Create the real source buffers if they don't exist by now since we
  30620. // finally are sure what tracks are contained in the source
  30621. if (!this.videoBuffer_ && !this.audioBuffer_) {
  30622. // Remove any codecs that may have been specified by default but
  30623. // are no longer applicable now
  30624. if (sortedSegments.video.bytes === 0) {
  30625. this.videoCodec_ = null;
  30626. }
  30627. if (sortedSegments.audio.bytes === 0) {
  30628. this.audioCodec_ = null;
  30629. }
  30630. this.createRealSourceBuffers_();
  30631. }
  30632. if (sortedSegments.audio.info) {
  30633. this.mediaSource_.trigger({
  30634. type: 'audioinfo',
  30635. info: sortedSegments.audio.info
  30636. });
  30637. }
  30638. if (sortedSegments.video.info) {
  30639. this.mediaSource_.trigger({
  30640. type: 'videoinfo',
  30641. info: sortedSegments.video.info
  30642. });
  30643. }
  30644. if (this.appendAudioInitSegment_) {
  30645. if (!this.audioDisabled_ && this.audioBuffer_) {
  30646. sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
  30647. sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
  30648. }
  30649. this.appendAudioInitSegment_ = false;
  30650. }
  30651. var triggerUpdateend = false; // Merge multiple video and audio segments into one and append
  30652. if (this.videoBuffer_ && sortedSegments.video.bytes) {
  30653. sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
  30654. sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
  30655. this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
  30656. } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
  30657. // The transmuxer did not return any bytes of video, meaning it was all trimmed
  30658. // for gop alignment. Since we have a video buffer and audio is disabled, updateend
  30659. // will never be triggered by this source buffer, which will cause contrib-hls
  30660. // to be stuck forever waiting for updateend. If audio is not disabled, updateend
  30661. // will be triggered by the audio buffer, which will be sent upwards since the video
  30662. // buffer will not be in an updating state.
  30663. triggerUpdateend = true;
  30664. } // Add text-track data for all
  30665. addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
  30666. if (!this.audioDisabled_ && this.audioBuffer_) {
  30667. this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
  30668. }
  30669. this.pendingBuffers_.length = 0;
  30670. if (triggerUpdateend) {
  30671. this.trigger('updateend');
  30672. } // We are no longer in the internal "updating" state
  30673. this.bufferUpdating_ = false;
  30674. }
  30675. /**
  30676. * Combine all segments into a single Uint8Array and then append them
  30677. * to the destination buffer
  30678. *
  30679. * @param {Object} segmentObj
  30680. * @param {SourceBuffer} destinationBuffer native source buffer to append data to
  30681. * @private
  30682. */
  30683. }, {
  30684. key: 'concatAndAppendSegments_',
  30685. value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
  30686. var offset = 0;
  30687. var tempBuffer = void 0;
  30688. if (segmentObj.bytes) {
  30689. tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array
  30690. segmentObj.segments.forEach(function (segment) {
  30691. tempBuffer.set(segment, offset);
  30692. offset += segment.byteLength;
  30693. });
  30694. try {
  30695. destinationBuffer.updating = true;
  30696. destinationBuffer.appendBuffer(tempBuffer);
  30697. } catch (error) {
  30698. if (this.mediaSource_.player_) {
  30699. this.mediaSource_.player_.error({
  30700. code: -3,
  30701. type: 'APPEND_BUFFER_ERR',
  30702. message: error.message,
  30703. originalError: error
  30704. });
  30705. }
  30706. }
  30707. }
  30708. }
  30709. /**
  30710. * Emulate the native mediasource function. abort any soureBuffer
  30711. * actions and throw out any un-appended data.
  30712. *
  30713. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
  30714. */
  30715. }, {
  30716. key: 'abort',
  30717. value: function abort() {
  30718. if (this.videoBuffer_) {
  30719. this.videoBuffer_.abort();
  30720. }
  30721. if (!this.audioDisabled_ && this.audioBuffer_) {
  30722. this.audioBuffer_.abort();
  30723. }
  30724. if (this.transmuxer_) {
  30725. this.transmuxer_.postMessage({
  30726. action: 'reset'
  30727. });
  30728. }
  30729. this.pendingBuffers_.length = 0;
  30730. this.bufferUpdating_ = false;
  30731. }
  30732. }]);
  30733. return VirtualSourceBuffer;
  30734. }(videojs$1.EventTarget);
  30735. /**
  30736. * @file html-media-source.js
  30737. */
  30738. /**
  30739. * Our MediaSource implementation in HTML, mimics native
  30740. * MediaSource where/if possible.
  30741. *
  30742. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
  30743. * @class HtmlMediaSource
  30744. * @extends videojs.EventTarget
  30745. */
  30746. var HtmlMediaSource = function (_videojs$EventTarget) {
  30747. inherits(HtmlMediaSource, _videojs$EventTarget);
  30748. function HtmlMediaSource() {
  30749. classCallCheck(this, HtmlMediaSource);
  30750. var _this = possibleConstructorReturn(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
  30751. var property = void 0;
  30752. _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default
  30753. for (property in _this.nativeMediaSource_) {
  30754. if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
  30755. _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
  30756. }
  30757. } // emulate `duration` and `seekable` until seeking can be
  30758. // handled uniformly for live streams
  30759. // see https://github.com/w3c/media-source/issues/5
  30760. _this.duration_ = NaN;
  30761. Object.defineProperty(_this, 'duration', {
  30762. get: function get$$1() {
  30763. if (this.duration_ === Infinity) {
  30764. return this.duration_;
  30765. }
  30766. return this.nativeMediaSource_.duration;
  30767. },
  30768. set: function set$$1(duration) {
  30769. this.duration_ = duration;
  30770. if (duration !== Infinity) {
  30771. this.nativeMediaSource_.duration = duration;
  30772. return;
  30773. }
  30774. }
  30775. });
  30776. Object.defineProperty(_this, 'seekable', {
  30777. get: function get$$1() {
  30778. if (this.duration_ === Infinity) {
  30779. return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
  30780. }
  30781. return this.nativeMediaSource_.seekable;
  30782. }
  30783. });
  30784. Object.defineProperty(_this, 'readyState', {
  30785. get: function get$$1() {
  30786. return this.nativeMediaSource_.readyState;
  30787. }
  30788. });
  30789. Object.defineProperty(_this, 'activeSourceBuffers', {
  30790. get: function get$$1() {
  30791. return this.activeSourceBuffers_;
  30792. }
  30793. }); // the list of virtual and native SourceBuffers created by this
  30794. // MediaSource
  30795. _this.sourceBuffers = [];
  30796. _this.activeSourceBuffers_ = [];
  30797. /**
  30798. * update the list of active source buffers based upon various
  30799. * imformation from HLS and video.js
  30800. *
  30801. * @private
  30802. */
  30803. _this.updateActiveSourceBuffers_ = function () {
  30804. // Retain the reference but empty the array
  30805. _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will
  30806. // be disabled based on the codec of the source buffer
  30807. if (_this.sourceBuffers.length === 1) {
  30808. var sourceBuffer = _this.sourceBuffers[0];
  30809. sourceBuffer.appendAudioInitSegment_ = true;
  30810. sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
  30811. _this.activeSourceBuffers_.push(sourceBuffer);
  30812. return;
  30813. } // There are 2 source buffers, a combined (possibly video only) source buffer and
  30814. // and an audio only source buffer.
  30815. // By default, the audio in the combined virtual source buffer is enabled
  30816. // and the audio-only source buffer (if it exists) is disabled.
  30817. var disableCombined = false;
  30818. var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects?
  30819. // safari may do something like this
  30820. for (var i = 0; i < _this.player_.audioTracks().length; i++) {
  30821. var track = _this.player_.audioTracks()[i];
  30822. if (track.enabled && track.kind !== 'main') {
  30823. // The enabled track is an alternate audio track so disable the audio in
  30824. // the combined source buffer and enable the audio-only source buffer.
  30825. disableCombined = true;
  30826. disableAudioOnly = false;
  30827. break;
  30828. }
  30829. }
  30830. _this.sourceBuffers.forEach(function (sourceBuffer, index) {
  30831. /* eslinst-disable */
  30832. // TODO once codecs are required, we can switch to using the codecs to determine
  30833. // what stream is the video stream, rather than relying on videoTracks
  30834. /* eslinst-enable */
  30835. sourceBuffer.appendAudioInitSegment_ = true;
  30836. if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  30837. // combined
  30838. sourceBuffer.audioDisabled_ = disableCombined;
  30839. } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
  30840. // If the "combined" source buffer is video only, then we do not want
  30841. // disable the audio-only source buffer (this is mostly for demuxed
  30842. // audio and video hls)
  30843. sourceBuffer.audioDisabled_ = true;
  30844. disableAudioOnly = false;
  30845. } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  30846. // audio only
  30847. // In the case of audio only with alternate audio and disableAudioOnly is true
  30848. // this means we want to disable the audio on the alternate audio sourcebuffer
  30849. // but not the main "combined" source buffer. The "combined" source buffer is
  30850. // always at index 0, so this ensures audio won't be disabled in both source
  30851. // buffers.
  30852. sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
  30853. if (sourceBuffer.audioDisabled_) {
  30854. return;
  30855. }
  30856. }
  30857. _this.activeSourceBuffers_.push(sourceBuffer);
  30858. });
  30859. };
  30860. _this.onPlayerMediachange_ = function () {
  30861. _this.sourceBuffers.forEach(function (sourceBuffer) {
  30862. sourceBuffer.appendAudioInitSegment_ = true;
  30863. });
  30864. };
  30865. _this.onHlsReset_ = function () {
  30866. _this.sourceBuffers.forEach(function (sourceBuffer) {
  30867. if (sourceBuffer.transmuxer_) {
  30868. sourceBuffer.transmuxer_.postMessage({
  30869. action: 'resetCaptions'
  30870. });
  30871. }
  30872. });
  30873. };
  30874. _this.onHlsSegmentTimeMapping_ = function (event) {
  30875. _this.sourceBuffers.forEach(function (buffer) {
  30876. return buffer.timeMapping_ = event.mapping;
  30877. });
  30878. }; // Re-emit MediaSource events on the polyfill
  30879. ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
  30880. this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
  30881. }, _this); // capture the associated player when the MediaSource is
  30882. // successfully attached
  30883. _this.on('sourceopen', function (event) {
  30884. // Get the player this MediaSource is attached to
  30885. var video = document.querySelector('[src="' + _this.url_ + '"]');
  30886. if (!video) {
  30887. return;
  30888. }
  30889. _this.player_ = videojs$1(video.parentNode);
  30890. if (!_this.player_) {
  30891. return;
  30892. } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
  30893. // resets its state and flushes the buffer
  30894. _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
  30895. // SegmentLoader inspects an MTS segment and has an accurate stream to display
  30896. // time mapping
  30897. _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
  30898. if (_this.player_.audioTracks && _this.player_.audioTracks()) {
  30899. _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
  30900. _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
  30901. _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
  30902. }
  30903. _this.player_.on('mediachange', _this.onPlayerMediachange_);
  30904. });
  30905. _this.on('sourceended', function (event) {
  30906. var duration = durationOfVideo(_this.duration);
  30907. for (var i = 0; i < _this.sourceBuffers.length; i++) {
  30908. var sourcebuffer = _this.sourceBuffers[i];
  30909. var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
  30910. if (cues && cues.length) {
  30911. cues[cues.length - 1].endTime = duration;
  30912. }
  30913. }
  30914. }); // explicitly terminate any WebWorkers that were created
  30915. // by SourceHandlers
  30916. _this.on('sourceclose', function (event) {
  30917. this.sourceBuffers.forEach(function (sourceBuffer) {
  30918. if (sourceBuffer.transmuxer_) {
  30919. sourceBuffer.transmuxer_.terminate();
  30920. }
  30921. });
  30922. this.sourceBuffers.length = 0;
  30923. if (!this.player_) {
  30924. return;
  30925. }
  30926. if (this.player_.audioTracks && this.player_.audioTracks()) {
  30927. this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
  30928. this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
  30929. this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
  30930. } // We can only change this if the player hasn't been disposed of yet
  30931. // because `off` eventually tries to use the el_ property. If it has
  30932. // been disposed of, then don't worry about it because there are no
  30933. // event handlers left to unbind anyway
  30934. if (this.player_.el_) {
  30935. this.player_.off('mediachange', this.onPlayerMediachange_);
  30936. }
  30937. if (this.player_.tech_ && this.player_.tech_.el_) {
  30938. this.player_.tech_.off('hls-reset', this.onHlsReset_);
  30939. this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
  30940. }
  30941. });
  30942. return _this;
  30943. }
  30944. /**
  30945. * Add a range that that can now be seeked to.
  30946. *
  30947. * @param {Double} start where to start the addition
  30948. * @param {Double} end where to end the addition
  30949. * @private
  30950. */
  30951. createClass(HtmlMediaSource, [{
  30952. key: 'addSeekableRange_',
  30953. value: function addSeekableRange_(start, end) {
  30954. var error = void 0;
  30955. if (this.duration !== Infinity) {
  30956. error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
  30957. error.name = 'InvalidStateError';
  30958. error.code = 11;
  30959. throw error;
  30960. }
  30961. if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
  30962. this.nativeMediaSource_.duration = end;
  30963. }
  30964. }
  30965. /**
  30966. * Add a source buffer to the media source.
  30967. *
  30968. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
  30969. * @param {String} type the content-type of the content
  30970. * @return {Object} the created source buffer
  30971. */
  30972. }, {
  30973. key: 'addSourceBuffer',
  30974. value: function addSourceBuffer(type) {
  30975. var buffer = void 0;
  30976. var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport
  30977. // stream segments into fragmented MP4s
  30978. if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
  30979. var codecs = [];
  30980. if (parsedType.parameters && parsedType.parameters.codecs) {
  30981. codecs = parsedType.parameters.codecs.split(',');
  30982. codecs = translateLegacyCodecs(codecs);
  30983. codecs = codecs.filter(function (codec) {
  30984. return isAudioCodec(codec) || isVideoCodec(codec);
  30985. });
  30986. }
  30987. if (codecs.length === 0) {
  30988. codecs = ['avc1.4d400d', 'mp4a.40.2'];
  30989. }
  30990. buffer = new VirtualSourceBuffer(this, codecs);
  30991. if (this.sourceBuffers.length !== 0) {
  30992. // If another VirtualSourceBuffer already exists, then we are creating a
  30993. // SourceBuffer for an alternate audio track and therefore we know that
  30994. // the source has both an audio and video track.
  30995. // That means we should trigger the manual creation of the real
  30996. // SourceBuffers instead of waiting for the transmuxer to return data
  30997. this.sourceBuffers[0].createRealSourceBuffers_();
  30998. buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if
  30999. // a second source buffer is ever created
  31000. this.sourceBuffers[0].audioDisabled_ = true;
  31001. }
  31002. } else {
  31003. // delegate to the native implementation
  31004. buffer = this.nativeMediaSource_.addSourceBuffer(type);
  31005. }
  31006. this.sourceBuffers.push(buffer);
  31007. return buffer;
  31008. }
  31009. }]);
  31010. return HtmlMediaSource;
  31011. }(videojs$1.EventTarget);
  31012. /**
  31013. * @file videojs-contrib-media-sources.js
  31014. */
  31015. var urlCount = 0; // ------------
  31016. // Media Source
  31017. // ------------
  31018. // store references to the media sources so they can be connected
  31019. // to a video element (a swf object)
  31020. // TODO: can we store this somewhere local to this module?
  31021. videojs$1.mediaSources = {};
  31022. /**
  31023. * Provide a method for a swf object to notify JS that a
  31024. * media source is now open.
  31025. *
  31026. * @param {String} msObjectURL string referencing the MSE Object URL
  31027. * @param {String} swfId the swf id
  31028. */
  31029. var open = function open(msObjectURL, swfId) {
  31030. var mediaSource = videojs$1.mediaSources[msObjectURL];
  31031. if (mediaSource) {
  31032. mediaSource.trigger({
  31033. type: 'sourceopen',
  31034. swfId: swfId
  31035. });
  31036. } else {
  31037. throw new Error('Media Source not found (Video.js)');
  31038. }
  31039. };
  31040. /**
  31041. * Check to see if the native MediaSource object exists and supports
  31042. * an MP4 container with both H.264 video and AAC-LC audio.
  31043. *
  31044. * @return {Boolean} if native media sources are supported
  31045. */
  31046. var supportsNativeMediaSources = function supportsNativeMediaSources() {
  31047. return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
  31048. };
  31049. /**
  31050. * An emulation of the MediaSource API so that we can support
  31051. * native and non-native functionality. returns an instance of
  31052. * HtmlMediaSource.
  31053. *
  31054. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
  31055. */
  31056. var MediaSource = function MediaSource() {
  31057. this.MediaSource = {
  31058. open: open,
  31059. supportsNativeMediaSources: supportsNativeMediaSources
  31060. };
  31061. if (supportsNativeMediaSources()) {
  31062. return new HtmlMediaSource();
  31063. }
  31064. throw new Error('Cannot use create a virtual MediaSource for this video');
  31065. };
  31066. MediaSource.open = open;
  31067. MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
  31068. /**
  31069. * A wrapper around the native URL for our MSE object
  31070. * implementation, this object is exposed under videojs.URL
  31071. *
  31072. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
  31073. */
  31074. var URL$1 = {
  31075. /**
  31076. * A wrapper around the native createObjectURL for our objects.
  31077. * This function maps a native or emulated mediaSource to a blob
  31078. * url so that it can be loaded into video.js
  31079. *
  31080. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
  31081. * @param {MediaSource} object the object to create a blob url to
  31082. */
  31083. createObjectURL: function createObjectURL(object) {
  31084. var objectUrlPrefix = 'blob:vjs-media-source/';
  31085. var url = void 0; // use the native MediaSource to generate an object URL
  31086. if (object instanceof HtmlMediaSource) {
  31087. url = window$1.URL.createObjectURL(object.nativeMediaSource_);
  31088. object.url_ = url;
  31089. return url;
  31090. } // if the object isn't an emulated MediaSource, delegate to the
  31091. // native implementation
  31092. if (!(object instanceof HtmlMediaSource)) {
  31093. url = window$1.URL.createObjectURL(object);
  31094. object.url_ = url;
  31095. return url;
  31096. } // build a URL that can be used to map back to the emulated
  31097. // MediaSource
  31098. url = objectUrlPrefix + urlCount;
  31099. urlCount++; // setup the mapping back to object
  31100. videojs$1.mediaSources[url] = object;
  31101. return url;
  31102. }
  31103. };
  31104. videojs$1.MediaSource = MediaSource;
  31105. videojs$1.URL = URL$1;
  31106. var EventTarget$1$1 = videojs$1.EventTarget,
  31107. mergeOptions$2 = videojs$1.mergeOptions;
  31108. /**
  31109. * Returns a new master manifest that is the result of merging an updated master manifest
  31110. * into the original version.
  31111. *
  31112. * @param {Object} oldMaster
  31113. * The old parsed mpd object
  31114. * @param {Object} newMaster
  31115. * The updated parsed mpd object
  31116. * @return {Object}
  31117. * A new object representing the original master manifest with the updated media
  31118. * playlists merged in
  31119. */
  31120. var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
  31121. var noChanges = void 0;
  31122. var update = mergeOptions$2(oldMaster, {
  31123. // These are top level properties that can be updated
  31124. duration: newMaster.duration,
  31125. minimumUpdatePeriod: newMaster.minimumUpdatePeriod
  31126. }); // First update the playlists in playlist list
  31127. for (var i = 0; i < newMaster.playlists.length; i++) {
  31128. var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
  31129. if (playlistUpdate) {
  31130. update = playlistUpdate;
  31131. } else {
  31132. noChanges = true;
  31133. }
  31134. } // Then update media group playlists
  31135. forEachMediaGroup(newMaster, function (properties, type, group, label) {
  31136. if (properties.playlists && properties.playlists.length) {
  31137. var uri = properties.playlists[0].uri;
  31138. var _playlistUpdate = updateMaster(update, properties.playlists[0]);
  31139. if (_playlistUpdate) {
  31140. update = _playlistUpdate; // update the playlist reference within media groups
  31141. update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
  31142. noChanges = false;
  31143. }
  31144. }
  31145. });
  31146. if (noChanges) {
  31147. return null;
  31148. }
  31149. return update;
  31150. };
  31151. var generateSidxKey = function generateSidxKey(sidxInfo) {
  31152. // should be non-inclusive
  31153. var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1;
  31154. return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd;
  31155. }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
  31156. // If the SIDXs have maps, the two maps should match,
  31157. // both `a` and `b` missing SIDXs is considered matching.
  31158. // If `a` or `b` but not both have a map, they aren't matching.
  31159. var equivalentSidx = function equivalentSidx(a, b) {
  31160. var neitherMap = Boolean(!a.map && !b.map);
  31161. var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
  31162. return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
  31163. }; // exported for testing
  31164. var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) {
  31165. var newSidxMapping = {};
  31166. for (var uri in playlists) {
  31167. var playlist = playlists[uri];
  31168. var currentSidxInfo = playlist.sidx;
  31169. if (currentSidxInfo) {
  31170. var key = generateSidxKey(currentSidxInfo);
  31171. if (!oldSidxMapping[key]) {
  31172. break;
  31173. }
  31174. var savedSidxInfo = oldSidxMapping[key].sidxInfo;
  31175. if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
  31176. newSidxMapping[key] = oldSidxMapping[key];
  31177. }
  31178. }
  31179. }
  31180. return newSidxMapping;
  31181. };
  31182. /**
  31183. * A function that filters out changed items as they need to be requested separately.
  31184. *
  31185. * The method is exported for testing
  31186. *
  31187. * @param {Object} masterXml the mpd XML
  31188. * @param {string} srcUrl the mpd url
  31189. * @param {Date} clientOffset a time difference between server and client (passed through and not used)
  31190. * @param {Object} oldSidxMapping the SIDX to compare against
  31191. */
  31192. var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) {
  31193. // Don't pass current sidx mapping
  31194. var master = mpdParser.parse(masterXml, {
  31195. manifestUri: srcUrl,
  31196. clientOffset: clientOffset
  31197. });
  31198. var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping);
  31199. var mediaGroupSidx = videoSidx;
  31200. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  31201. if (properties.playlists && properties.playlists.length) {
  31202. var playlists = properties.playlists;
  31203. mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
  31204. }
  31205. });
  31206. return mediaGroupSidx;
  31207. }; // exported for testing
  31208. var requestSidx_ = function requestSidx_(sidxRange, playlist, xhr$$1, options, finishProcessingFn) {
  31209. var sidxInfo = {
  31210. // resolve the segment URL relative to the playlist
  31211. uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri),
  31212. // resolvedUri: sidxRange.resolvedUri,
  31213. byterange: sidxRange.byterange,
  31214. // the segment's playlist
  31215. playlist: playlist
  31216. };
  31217. var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, {
  31218. responseType: 'arraybuffer',
  31219. headers: segmentXhrHeaders(sidxInfo)
  31220. });
  31221. return xhr$$1(sidxRequestOptions, finishProcessingFn);
  31222. };
  31223. var DashPlaylistLoader = function (_EventTarget) {
  31224. inherits(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
  31225. // playlist loader setups from media groups will expect to be able to pass a playlist
  31226. // (since there aren't external URLs to media playlists with DASH)
  31227. function DashPlaylistLoader(srcUrlOrPlaylist, hls) {
  31228. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  31229. var masterPlaylistLoader = arguments[3];
  31230. classCallCheck(this, DashPlaylistLoader);
  31231. var _this = possibleConstructorReturn(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
  31232. var _options$withCredenti = options.withCredentials,
  31233. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  31234. _options$handleManife = options.handleManifestRedirects,
  31235. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  31236. _this.hls_ = hls;
  31237. _this.withCredentials = withCredentials;
  31238. _this.handleManifestRedirects = handleManifestRedirects;
  31239. if (!srcUrlOrPlaylist) {
  31240. throw new Error('A non-empty playlist URL or playlist is required');
  31241. } // event naming?
  31242. _this.on('minimumUpdatePeriod', function () {
  31243. _this.refreshXml_();
  31244. }); // live playlist staleness timeout
  31245. _this.on('mediaupdatetimeout', function () {
  31246. _this.refreshMedia_(_this.media().uri);
  31247. });
  31248. _this.state = 'HAVE_NOTHING';
  31249. _this.loadedPlaylists_ = {}; // initialize the loader state
  31250. // The masterPlaylistLoader will be created with a string
  31251. if (typeof srcUrlOrPlaylist === 'string') {
  31252. _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
  31253. // once multi-period is refactored
  31254. _this.sidxMapping_ = {};
  31255. return possibleConstructorReturn(_this);
  31256. }
  31257. _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist);
  31258. return _this;
  31259. }
  31260. createClass(DashPlaylistLoader, [{
  31261. key: 'setupChildLoader',
  31262. value: function setupChildLoader(masterPlaylistLoader, playlist) {
  31263. this.masterPlaylistLoader_ = masterPlaylistLoader;
  31264. this.childPlaylist_ = playlist;
  31265. }
  31266. }, {
  31267. key: 'dispose',
  31268. value: function dispose() {
  31269. this.stopRequest();
  31270. this.loadedPlaylists_ = {};
  31271. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  31272. window$1.clearTimeout(this.mediaRequest_);
  31273. window$1.clearTimeout(this.mediaUpdateTimeout);
  31274. }
  31275. }, {
  31276. key: 'hasPendingRequest',
  31277. value: function hasPendingRequest() {
  31278. return this.request || this.mediaRequest_;
  31279. }
  31280. }, {
  31281. key: 'stopRequest',
  31282. value: function stopRequest() {
  31283. if (this.request) {
  31284. var oldRequest = this.request;
  31285. this.request = null;
  31286. oldRequest.onreadystatechange = null;
  31287. oldRequest.abort();
  31288. }
  31289. }
  31290. }, {
  31291. key: 'sidxRequestFinished_',
  31292. value: function sidxRequestFinished_(playlist, master, startingState, doneFn) {
  31293. var _this2 = this;
  31294. return function (err, request) {
  31295. // disposed
  31296. if (!_this2.request) {
  31297. return;
  31298. } // pending request is cleared
  31299. _this2.request = null;
  31300. if (err) {
  31301. _this2.error = {
  31302. status: request.status,
  31303. message: 'DASH playlist request error at URL: ' + playlist.uri,
  31304. response: request.response,
  31305. // MEDIA_ERR_NETWORK
  31306. code: 2
  31307. };
  31308. if (startingState) {
  31309. _this2.state = startingState;
  31310. }
  31311. _this2.trigger('error');
  31312. return doneFn(master, null);
  31313. }
  31314. var bytes = new Uint8Array(request.response);
  31315. var sidx = mp4Inspector.parseSidx(bytes.subarray(8));
  31316. return doneFn(master, sidx);
  31317. };
  31318. }
  31319. }, {
  31320. key: 'media',
  31321. value: function media(playlist) {
  31322. var _this3 = this; // getter
  31323. if (!playlist) {
  31324. return this.media_;
  31325. } // setter
  31326. if (this.state === 'HAVE_NOTHING') {
  31327. throw new Error('Cannot switch media playlist from ' + this.state);
  31328. }
  31329. var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
  31330. if (typeof playlist === 'string') {
  31331. if (!this.master.playlists[playlist]) {
  31332. throw new Error('Unknown playlist URI: ' + playlist);
  31333. }
  31334. playlist = this.master.playlists[playlist];
  31335. }
  31336. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately
  31337. if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) {
  31338. this.state = 'HAVE_METADATA';
  31339. this.media_ = playlist; // trigger media change if the active media has been updated
  31340. if (mediaChange) {
  31341. this.trigger('mediachanging');
  31342. this.trigger('mediachange');
  31343. }
  31344. return;
  31345. } // switching to the active playlist is a no-op
  31346. if (!mediaChange) {
  31347. return;
  31348. } // switching from an already loaded playlist
  31349. if (this.media_) {
  31350. this.trigger('mediachanging');
  31351. }
  31352. if (!playlist.sidx) {
  31353. // Continue asynchronously if there is no sidx
  31354. // wait one tick to allow haveMaster to run first on a child loader
  31355. this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, {
  31356. startingState: startingState,
  31357. playlist: playlist
  31358. }), 0); // exit early and don't do sidx work
  31359. return;
  31360. } // we have sidx mappings
  31361. var oldMaster = void 0;
  31362. var sidxMapping = void 0; // sidxMapping is used when parsing the masterXml, so store
  31363. // it on the masterPlaylistLoader
  31364. if (this.masterPlaylistLoader_) {
  31365. oldMaster = this.masterPlaylistLoader_.master;
  31366. sidxMapping = this.masterPlaylistLoader_.sidxMapping_;
  31367. } else {
  31368. oldMaster = this.master;
  31369. sidxMapping = this.sidxMapping_;
  31370. }
  31371. var sidxKey = generateSidxKey(playlist.sidx);
  31372. sidxMapping[sidxKey] = {
  31373. sidxInfo: playlist.sidx
  31374. };
  31375. this.request = requestSidx_(playlist.sidx, playlist, this.hls_.xhr, {
  31376. handleManifestRedirects: this.handleManifestRedirects
  31377. }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) {
  31378. if (!newMaster || !sidx) {
  31379. throw new Error('failed to request sidx');
  31380. } // update loader's sidxMapping with parsed sidx box
  31381. sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata
  31382. _this3.haveMetadata({
  31383. startingState: startingState,
  31384. playlist: newMaster.playlists[playlist.uri]
  31385. });
  31386. }));
  31387. }
  31388. }, {
  31389. key: 'haveMetadata',
  31390. value: function haveMetadata(_ref) {
  31391. var startingState = _ref.startingState,
  31392. playlist = _ref.playlist;
  31393. this.state = 'HAVE_METADATA';
  31394. this.loadedPlaylists_[playlist.uri] = playlist;
  31395. this.mediaRequest_ = null; // This will trigger loadedplaylist
  31396. this.refreshMedia_(playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  31397. // to resolve setup of media groups
  31398. if (startingState === 'HAVE_MASTER') {
  31399. this.trigger('loadedmetadata');
  31400. } else {
  31401. // trigger media change if the active media has been updated
  31402. this.trigger('mediachange');
  31403. }
  31404. }
  31405. }, {
  31406. key: 'pause',
  31407. value: function pause() {
  31408. this.stopRequest();
  31409. window$1.clearTimeout(this.mediaUpdateTimeout);
  31410. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  31411. if (this.state === 'HAVE_NOTHING') {
  31412. // If we pause the loader before any data has been retrieved, its as if we never
  31413. // started, so reset to an unstarted state.
  31414. this.started = false;
  31415. }
  31416. }
  31417. }, {
  31418. key: 'load',
  31419. value: function load(isFinalRendition) {
  31420. var _this4 = this;
  31421. window$1.clearTimeout(this.mediaUpdateTimeout);
  31422. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  31423. var media = this.media();
  31424. if (isFinalRendition) {
  31425. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  31426. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  31427. return _this4.load();
  31428. }, delay);
  31429. return;
  31430. } // because the playlists are internal to the manifest, load should either load the
  31431. // main manifest, or do nothing but trigger an event
  31432. if (!this.started) {
  31433. this.start();
  31434. return;
  31435. }
  31436. this.trigger('loadedplaylist');
  31437. }
  31438. /**
  31439. * Parses the master xml string and updates playlist uri references
  31440. *
  31441. * @return {Object}
  31442. * The parsed mpd manifest object
  31443. */
  31444. }, {
  31445. key: 'parseMasterXml',
  31446. value: function parseMasterXml() {
  31447. var master = mpdParser.parse(this.masterXml_, {
  31448. manifestUri: this.srcUrl,
  31449. clientOffset: this.clientOffset_,
  31450. sidxMapping: this.sidxMapping_
  31451. });
  31452. master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH
  31453. // but reference playlists by their URI throughout the project
  31454. // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
  31455. for (var i = 0; i < master.playlists.length; i++) {
  31456. var phonyUri = 'placeholder-uri-' + i;
  31457. master.playlists[i].uri = phonyUri; // set up by URI references
  31458. master.playlists[phonyUri] = master.playlists[i];
  31459. } // set up phony URIs for the media group playlists since we won't have external
  31460. // URIs for DASH but reference playlists by their URI throughout the project
  31461. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  31462. if (properties.playlists && properties.playlists.length) {
  31463. var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
  31464. properties.playlists[0].uri = _phonyUri; // setup URI references
  31465. master.playlists[_phonyUri] = properties.playlists[0];
  31466. }
  31467. });
  31468. setupMediaPlaylists(master);
  31469. resolveMediaGroupUris(master);
  31470. return master;
  31471. }
  31472. }, {
  31473. key: 'start',
  31474. value: function start() {
  31475. var _this5 = this;
  31476. this.started = true; // We don't need to request the master manifest again
  31477. // Call this asynchronously to match the xhr request behavior below
  31478. if (this.masterPlaylistLoader_) {
  31479. this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0);
  31480. return;
  31481. } // request the specified URL
  31482. this.request = this.hls_.xhr({
  31483. uri: this.srcUrl,
  31484. withCredentials: this.withCredentials
  31485. }, function (error, req) {
  31486. // disposed
  31487. if (!_this5.request) {
  31488. return;
  31489. } // clear the loader's request reference
  31490. _this5.request = null;
  31491. if (error) {
  31492. _this5.error = {
  31493. status: req.status,
  31494. message: 'DASH playlist request error at URL: ' + _this5.srcUrl,
  31495. responseText: req.responseText,
  31496. // MEDIA_ERR_NETWORK
  31497. code: 2
  31498. };
  31499. if (_this5.state === 'HAVE_NOTHING') {
  31500. _this5.started = false;
  31501. }
  31502. return _this5.trigger('error');
  31503. }
  31504. _this5.masterXml_ = req.responseText;
  31505. if (req.responseHeaders && req.responseHeaders.date) {
  31506. _this5.masterLoaded_ = Date.parse(req.responseHeaders.date);
  31507. } else {
  31508. _this5.masterLoaded_ = Date.now();
  31509. }
  31510. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  31511. _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5));
  31512. });
  31513. }
  31514. /**
  31515. * Parses the master xml for UTCTiming node to sync the client clock to the server
  31516. * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
  31517. *
  31518. * @param {Function} done
  31519. * Function to call when clock sync has completed
  31520. */
  31521. }, {
  31522. key: 'syncClientServerClock_',
  31523. value: function syncClientServerClock_(done) {
  31524. var _this6 = this;
  31525. var utcTiming = mpdParser.parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
  31526. // server clock
  31527. if (utcTiming === null) {
  31528. this.clientOffset_ = this.masterLoaded_ - Date.now();
  31529. return done();
  31530. }
  31531. if (utcTiming.method === 'DIRECT') {
  31532. this.clientOffset_ = utcTiming.value - Date.now();
  31533. return done();
  31534. }
  31535. this.request = this.hls_.xhr({
  31536. uri: resolveUrl(this.srcUrl, utcTiming.value),
  31537. method: utcTiming.method,
  31538. withCredentials: this.withCredentials
  31539. }, function (error, req) {
  31540. // disposed
  31541. if (!_this6.request) {
  31542. return;
  31543. }
  31544. if (error) {
  31545. // sync request failed, fall back to using date header from mpd
  31546. // TODO: log warning
  31547. _this6.clientOffset_ = _this6.masterLoaded_ - Date.now();
  31548. return done();
  31549. }
  31550. var serverTime = void 0;
  31551. if (utcTiming.method === 'HEAD') {
  31552. if (!req.responseHeaders || !req.responseHeaders.date) {
  31553. // expected date header not preset, fall back to using date header from mpd
  31554. // TODO: log warning
  31555. serverTime = _this6.masterLoaded_;
  31556. } else {
  31557. serverTime = Date.parse(req.responseHeaders.date);
  31558. }
  31559. } else {
  31560. serverTime = Date.parse(req.responseText);
  31561. }
  31562. _this6.clientOffset_ = serverTime - Date.now();
  31563. done();
  31564. });
  31565. }
  31566. }, {
  31567. key: 'haveMaster_',
  31568. value: function haveMaster_() {
  31569. this.state = 'HAVE_MASTER'; // clear media request
  31570. this.mediaRequest_ = null;
  31571. if (!this.masterPlaylistLoader_) {
  31572. this.master = this.parseMasterXml(); // We have the master playlist at this point, so
  31573. // trigger this to allow MasterPlaylistController
  31574. // to make an initial playlist selection
  31575. this.trigger('loadedplaylist');
  31576. } else if (!this.media_) {
  31577. // no media playlist was specifically selected so select
  31578. // the one the child playlist loader was created with
  31579. this.media(this.childPlaylist_);
  31580. }
  31581. }
  31582. /**
  31583. * Handler for after client/server clock synchronization has happened. Sets up
  31584. * xml refresh timer if specificed by the manifest.
  31585. */
  31586. }, {
  31587. key: 'onClientServerClockSync_',
  31588. value: function onClientServerClockSync_() {
  31589. var _this7 = this;
  31590. this.haveMaster_();
  31591. if (!this.hasPendingRequest() && !this.media_) {
  31592. this.media(this.master.playlists[0]);
  31593. } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
  31594. // be refreshed when this is the case. The inter-op guide says that when the
  31595. // minimumUpdatePeriod is 0, the manifest should outline all currently available
  31596. // segments, but future segments may require an update. I think a good solution
  31597. // would be to update the manifest at the same rate that the media playlists
  31598. // are "refreshed", i.e. every targetDuration.
  31599. if (this.master && this.master.minimumUpdatePeriod) {
  31600. this.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  31601. _this7.trigger('minimumUpdatePeriod');
  31602. }, this.master.minimumUpdatePeriod);
  31603. }
  31604. }
  31605. /**
  31606. * Sends request to refresh the master xml and updates the parsed master manifest
  31607. * TODO: Does the client offset need to be recalculated when the xml is refreshed?
  31608. */
  31609. }, {
  31610. key: 'refreshXml_',
  31611. value: function refreshXml_() {
  31612. var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when
  31613. // sidx is implemented
  31614. this.request = this.hls_.xhr({
  31615. uri: this.srcUrl,
  31616. withCredentials: this.withCredentials
  31617. }, function (error, req) {
  31618. // disposed
  31619. if (!_this8.request) {
  31620. return;
  31621. } // clear the loader's request reference
  31622. _this8.request = null;
  31623. if (error) {
  31624. _this8.error = {
  31625. status: req.status,
  31626. message: 'DASH playlist request error at URL: ' + _this8.srcUrl,
  31627. responseText: req.responseText,
  31628. // MEDIA_ERR_NETWORK
  31629. code: 2
  31630. };
  31631. if (_this8.state === 'HAVE_NOTHING') {
  31632. _this8.started = false;
  31633. }
  31634. return _this8.trigger('error');
  31635. }
  31636. _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping
  31637. _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_);
  31638. var master = _this8.parseMasterXml();
  31639. var updatedMaster = updateMaster$1(_this8.master, master);
  31640. if (updatedMaster) {
  31641. var sidxKey = generateSidxKey(_this8.media().sidx); // the sidx was updated, so the previous mapping was removed
  31642. if (!_this8.sidxMapping_[sidxKey]) {
  31643. var playlist = _this8.media();
  31644. _this8.request = requestSidx_(playlist.sidx, playlist, _this8.hls_.xhr, {
  31645. handleManifestRedirects: _this8.handleManifestRedirects
  31646. }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) {
  31647. if (!newMaster || !sidx) {
  31648. throw new Error('failed to request sidx on minimumUpdatePeriod');
  31649. } // update loader's sidxMapping with parsed sidx box
  31650. _this8.sidxMapping_[sidxKey].sidx = sidx;
  31651. _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  31652. _this8.trigger('minimumUpdatePeriod');
  31653. }, _this8.master.minimumUpdatePeriod); // TODO: do we need to reload the current playlist?
  31654. _this8.refreshMedia_(_this8.media().uri);
  31655. return;
  31656. }));
  31657. } else {
  31658. _this8.master = updatedMaster;
  31659. }
  31660. }
  31661. _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  31662. _this8.trigger('minimumUpdatePeriod');
  31663. }, _this8.master.minimumUpdatePeriod);
  31664. });
  31665. }
  31666. /**
  31667. * Refreshes the media playlist by re-parsing the master xml and updating playlist
  31668. * references. If this is an alternate loader, the updated parsed manifest is retrieved
  31669. * from the master loader.
  31670. */
  31671. }, {
  31672. key: 'refreshMedia_',
  31673. value: function refreshMedia_(mediaUri) {
  31674. var _this9 = this;
  31675. if (!mediaUri) {
  31676. throw new Error('refreshMedia_ must take a media uri');
  31677. }
  31678. var oldMaster = void 0;
  31679. var newMaster = void 0;
  31680. if (this.masterPlaylistLoader_) {
  31681. oldMaster = this.masterPlaylistLoader_.master;
  31682. newMaster = this.masterPlaylistLoader_.parseMasterXml();
  31683. } else {
  31684. oldMaster = this.master;
  31685. newMaster = this.parseMasterXml();
  31686. }
  31687. var updatedMaster = updateMaster$1(oldMaster, newMaster);
  31688. if (updatedMaster) {
  31689. if (this.masterPlaylistLoader_) {
  31690. this.masterPlaylistLoader_.master = updatedMaster;
  31691. } else {
  31692. this.master = updatedMaster;
  31693. }
  31694. this.media_ = updatedMaster.playlists[mediaUri];
  31695. } else {
  31696. this.media_ = newMaster.playlists[mediaUri];
  31697. this.trigger('playlistunchanged');
  31698. }
  31699. if (!this.media().endList) {
  31700. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  31701. _this9.trigger('mediaupdatetimeout');
  31702. }, refreshDelay(this.media(), !!updatedMaster));
  31703. }
  31704. this.trigger('loadedplaylist');
  31705. }
  31706. }]);
  31707. return DashPlaylistLoader;
  31708. }(EventTarget$1$1);
  31709. var logger = function logger(source) {
  31710. if (videojs$1.log.debug) {
  31711. return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
  31712. }
  31713. return function () {};
  31714. };
  31715. function noop() {}
  31716. /**
  31717. * @file source-updater.js
  31718. */
  31719. /**
  31720. * A queue of callbacks to be serialized and applied when a
  31721. * MediaSource and its associated SourceBuffers are not in the
  31722. * updating state. It is used by the segment loader to update the
  31723. * underlying SourceBuffers when new data is loaded, for instance.
  31724. *
  31725. * @class SourceUpdater
  31726. * @param {MediaSource} mediaSource the MediaSource to create the
  31727. * SourceBuffer from
  31728. * @param {String} mimeType the desired MIME type of the underlying
  31729. * SourceBuffer
  31730. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
  31731. * added to the media source
  31732. */
  31733. var SourceUpdater = function () {
  31734. function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
  31735. classCallCheck(this, SourceUpdater);
  31736. this.callbacks_ = [];
  31737. this.pendingCallback_ = null;
  31738. this.timestampOffset_ = 0;
  31739. this.mediaSource = mediaSource;
  31740. this.processedAppend_ = false;
  31741. this.type_ = type;
  31742. this.mimeType_ = mimeType;
  31743. this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
  31744. if (mediaSource.readyState === 'closed') {
  31745. mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
  31746. } else {
  31747. this.createSourceBuffer_(mimeType, sourceBufferEmitter);
  31748. }
  31749. }
  31750. createClass(SourceUpdater, [{
  31751. key: 'createSourceBuffer_',
  31752. value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
  31753. var _this = this;
  31754. this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
  31755. this.logger_('created SourceBuffer');
  31756. if (sourceBufferEmitter) {
  31757. sourceBufferEmitter.trigger('sourcebufferadded');
  31758. if (this.mediaSource.sourceBuffers.length < 2) {
  31759. // There's another source buffer we must wait for before we can start updating
  31760. // our own (or else we can get into a bad state, i.e., appending video/audio data
  31761. // before the other video/audio source buffer is available and leading to a video
  31762. // or audio only buffer).
  31763. sourceBufferEmitter.on('sourcebufferadded', function () {
  31764. _this.start_();
  31765. });
  31766. return;
  31767. }
  31768. }
  31769. this.start_();
  31770. }
  31771. }, {
  31772. key: 'start_',
  31773. value: function start_() {
  31774. var _this2 = this;
  31775. this.started_ = true; // run completion handlers and process callbacks as updateend
  31776. // events fire
  31777. this.onUpdateendCallback_ = function () {
  31778. var pendingCallback = _this2.pendingCallback_;
  31779. _this2.pendingCallback_ = null;
  31780. _this2.sourceBuffer_.removing = false;
  31781. _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
  31782. if (pendingCallback) {
  31783. pendingCallback();
  31784. }
  31785. _this2.runCallback_();
  31786. };
  31787. this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
  31788. this.runCallback_();
  31789. }
  31790. /**
  31791. * Aborts the current segment and resets the segment parser.
  31792. *
  31793. * @param {Function} done function to call when done
  31794. * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
  31795. */
  31796. }, {
  31797. key: 'abort',
  31798. value: function abort(done) {
  31799. var _this3 = this;
  31800. if (this.processedAppend_) {
  31801. this.queueCallback_(function () {
  31802. _this3.sourceBuffer_.abort();
  31803. }, done);
  31804. }
  31805. }
  31806. /**
  31807. * Queue an update to append an ArrayBuffer.
  31808. *
  31809. * @param {ArrayBuffer} bytes
  31810. * @param {Function} done the function to call when done
  31811. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
  31812. */
  31813. }, {
  31814. key: 'appendBuffer',
  31815. value: function appendBuffer(config, done) {
  31816. var _this4 = this;
  31817. this.processedAppend_ = true;
  31818. this.queueCallback_(function () {
  31819. if (config.videoSegmentTimingInfoCallback) {
  31820. _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  31821. }
  31822. _this4.sourceBuffer_.appendBuffer(config.bytes);
  31823. }, function () {
  31824. if (config.videoSegmentTimingInfoCallback) {
  31825. _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  31826. }
  31827. done();
  31828. });
  31829. }
  31830. /**
  31831. * Indicates what TimeRanges are buffered in the managed SourceBuffer.
  31832. *
  31833. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
  31834. */
  31835. }, {
  31836. key: 'buffered',
  31837. value: function buffered() {
  31838. if (!this.sourceBuffer_) {
  31839. return videojs$1.createTimeRanges();
  31840. }
  31841. return this.sourceBuffer_.buffered;
  31842. }
  31843. /**
  31844. * Queue an update to remove a time range from the buffer.
  31845. *
  31846. * @param {Number} start where to start the removal
  31847. * @param {Number} end where to end the removal
  31848. * @param {Function} [done=noop] optional callback to be executed when the remove
  31849. * operation is complete
  31850. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
  31851. */
  31852. }, {
  31853. key: 'remove',
  31854. value: function remove(start, end) {
  31855. var _this5 = this;
  31856. var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop;
  31857. if (this.processedAppend_) {
  31858. this.queueCallback_(function () {
  31859. _this5.logger_('remove [' + start + ' => ' + end + ']');
  31860. _this5.sourceBuffer_.removing = true;
  31861. _this5.sourceBuffer_.remove(start, end);
  31862. }, done);
  31863. }
  31864. }
  31865. /**
  31866. * Whether the underlying sourceBuffer is updating or not
  31867. *
  31868. * @return {Boolean} the updating status of the SourceBuffer
  31869. */
  31870. }, {
  31871. key: 'updating',
  31872. value: function updating() {
  31873. // we are updating if the sourcebuffer is updating or
  31874. return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop
  31875. !!this.pendingCallback_ && this.pendingCallback_ !== noop;
  31876. }
  31877. /**
  31878. * Set/get the timestampoffset on the SourceBuffer
  31879. *
  31880. * @return {Number} the timestamp offset
  31881. */
  31882. }, {
  31883. key: 'timestampOffset',
  31884. value: function timestampOffset(offset) {
  31885. var _this6 = this;
  31886. if (typeof offset !== 'undefined') {
  31887. this.queueCallback_(function () {
  31888. _this6.sourceBuffer_.timestampOffset = offset;
  31889. });
  31890. this.timestampOffset_ = offset;
  31891. }
  31892. return this.timestampOffset_;
  31893. }
  31894. /**
  31895. * Queue a callback to run
  31896. */
  31897. }, {
  31898. key: 'queueCallback_',
  31899. value: function queueCallback_(callback, done) {
  31900. this.callbacks_.push([callback.bind(this), done]);
  31901. this.runCallback_();
  31902. }
  31903. /**
  31904. * Run a queued callback
  31905. */
  31906. }, {
  31907. key: 'runCallback_',
  31908. value: function runCallback_() {
  31909. var callbacks = void 0;
  31910. if (!this.updating() && this.callbacks_.length && this.started_) {
  31911. callbacks = this.callbacks_.shift();
  31912. this.pendingCallback_ = callbacks[1];
  31913. callbacks[0]();
  31914. }
  31915. }
  31916. /**
  31917. * dispose of the source updater and the underlying sourceBuffer
  31918. */
  31919. }, {
  31920. key: 'dispose',
  31921. value: function dispose() {
  31922. var _this7 = this;
  31923. var disposeFn = function disposeFn() {
  31924. if (_this7.sourceBuffer_ && _this7.mediaSource.readyState === 'open') {
  31925. _this7.sourceBuffer_.abort();
  31926. }
  31927. _this7.sourceBuffer_.removeEventListener('updateend', disposeFn);
  31928. };
  31929. this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
  31930. if (this.sourceBuffer_.removing) {
  31931. this.sourceBuffer_.addEventListener('updateend', disposeFn);
  31932. } else {
  31933. disposeFn();
  31934. }
  31935. }
  31936. }]);
  31937. return SourceUpdater;
  31938. }();
  31939. var Config = {
  31940. GOAL_BUFFER_LENGTH: 30,
  31941. MAX_GOAL_BUFFER_LENGTH: 60,
  31942. GOAL_BUFFER_LENGTH_RATE: 1,
  31943. // 0.5 MB/s
  31944. INITIAL_BANDWIDTH: 4194304,
  31945. // A fudge factor to apply to advertised playlist bitrates to account for
  31946. // temporary flucations in client bandwidth
  31947. BANDWIDTH_VARIANCE: 1.2,
  31948. // How much of the buffer must be filled before we consider upswitching
  31949. BUFFER_LOW_WATER_LINE: 0,
  31950. MAX_BUFFER_LOW_WATER_LINE: 30,
  31951. BUFFER_LOW_WATER_LINE_RATE: 1
  31952. };
  31953. var REQUEST_ERRORS = {
  31954. FAILURE: 2,
  31955. TIMEOUT: -101,
  31956. ABORTED: -102
  31957. };
  31958. /**
  31959. * Abort all requests
  31960. *
  31961. * @param {Object} activeXhrs - an object that tracks all XHR requests
  31962. */
  31963. var abortAll = function abortAll(activeXhrs) {
  31964. activeXhrs.forEach(function (xhr$$1) {
  31965. xhr$$1.abort();
  31966. });
  31967. };
  31968. /**
  31969. * Gather important bandwidth stats once a request has completed
  31970. *
  31971. * @param {Object} request - the XHR request from which to gather stats
  31972. */
  31973. var getRequestStats = function getRequestStats(request) {
  31974. return {
  31975. bandwidth: request.bandwidth,
  31976. bytesReceived: request.bytesReceived || 0,
  31977. roundTripTime: request.roundTripTime || 0
  31978. };
  31979. };
  31980. /**
  31981. * If possible gather bandwidth stats as a request is in
  31982. * progress
  31983. *
  31984. * @param {Event} progressEvent - an event object from an XHR's progress event
  31985. */
  31986. var getProgressStats = function getProgressStats(progressEvent) {
  31987. var request = progressEvent.target;
  31988. var roundTripTime = Date.now() - request.requestTime;
  31989. var stats = {
  31990. bandwidth: Infinity,
  31991. bytesReceived: 0,
  31992. roundTripTime: roundTripTime || 0
  31993. };
  31994. stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
  31995. // because we should only use bandwidth stats on progress to determine when
  31996. // abort a request early due to insufficient bandwidth
  31997. stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
  31998. return stats;
  31999. };
  32000. /**
  32001. * Handle all error conditions in one place and return an object
  32002. * with all the information
  32003. *
  32004. * @param {Error|null} error - if non-null signals an error occured with the XHR
  32005. * @param {Object} request - the XHR request that possibly generated the error
  32006. */
  32007. var handleErrors = function handleErrors(error, request) {
  32008. if (request.timedout) {
  32009. return {
  32010. status: request.status,
  32011. message: 'HLS request timed-out at URL: ' + request.uri,
  32012. code: REQUEST_ERRORS.TIMEOUT,
  32013. xhr: request
  32014. };
  32015. }
  32016. if (request.aborted) {
  32017. return {
  32018. status: request.status,
  32019. message: 'HLS request aborted at URL: ' + request.uri,
  32020. code: REQUEST_ERRORS.ABORTED,
  32021. xhr: request
  32022. };
  32023. }
  32024. if (error) {
  32025. return {
  32026. status: request.status,
  32027. message: 'HLS request errored at URL: ' + request.uri,
  32028. code: REQUEST_ERRORS.FAILURE,
  32029. xhr: request
  32030. };
  32031. }
  32032. return null;
  32033. };
  32034. /**
  32035. * Handle responses for key data and convert the key data to the correct format
  32036. * for the decryption step later
  32037. *
  32038. * @param {Object} segment - a simplified copy of the segmentInfo object
  32039. * from SegmentLoader
  32040. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  32041. * this request
  32042. */
  32043. var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
  32044. return function (error, request) {
  32045. var response = request.response;
  32046. var errorObj = handleErrors(error, request);
  32047. if (errorObj) {
  32048. return finishProcessingFn(errorObj, segment);
  32049. }
  32050. if (response.byteLength !== 16) {
  32051. return finishProcessingFn({
  32052. status: request.status,
  32053. message: 'Invalid HLS key at URL: ' + request.uri,
  32054. code: REQUEST_ERRORS.FAILURE,
  32055. xhr: request
  32056. }, segment);
  32057. }
  32058. var view = new DataView(response);
  32059. segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
  32060. return finishProcessingFn(null, segment);
  32061. };
  32062. };
  32063. /**
  32064. * Handle init-segment responses
  32065. *
  32066. * @param {Object} segment - a simplified copy of the segmentInfo object
  32067. * from SegmentLoader
  32068. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  32069. * this request
  32070. */
  32071. var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) {
  32072. return function (error, request) {
  32073. var response = request.response;
  32074. var errorObj = handleErrors(error, request);
  32075. if (errorObj) {
  32076. return finishProcessingFn(errorObj, segment);
  32077. } // stop processing if received empty content
  32078. if (response.byteLength === 0) {
  32079. return finishProcessingFn({
  32080. status: request.status,
  32081. message: 'Empty HLS segment content at URL: ' + request.uri,
  32082. code: REQUEST_ERRORS.FAILURE,
  32083. xhr: request
  32084. }, segment);
  32085. }
  32086. segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet
  32087. if (!captionParser.isInitialized()) {
  32088. captionParser.init();
  32089. }
  32090. segment.map.timescales = mp4probe.timescale(segment.map.bytes);
  32091. segment.map.videoTrackIds = mp4probe.videoTrackIds(segment.map.bytes);
  32092. return finishProcessingFn(null, segment);
  32093. };
  32094. };
  32095. /**
  32096. * Response handler for segment-requests being sure to set the correct
  32097. * property depending on whether the segment is encryped or not
  32098. * Also records and keeps track of stats that are used for ABR purposes
  32099. *
  32100. * @param {Object} segment - a simplified copy of the segmentInfo object
  32101. * from SegmentLoader
  32102. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  32103. * this request
  32104. */
  32105. var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) {
  32106. return function (error, request) {
  32107. var response = request.response;
  32108. var errorObj = handleErrors(error, request);
  32109. var parsed = void 0;
  32110. if (errorObj) {
  32111. return finishProcessingFn(errorObj, segment);
  32112. } // stop processing if received empty content
  32113. if (response.byteLength === 0) {
  32114. return finishProcessingFn({
  32115. status: request.status,
  32116. message: 'Empty HLS segment content at URL: ' + request.uri,
  32117. code: REQUEST_ERRORS.FAILURE,
  32118. xhr: request
  32119. }, segment);
  32120. }
  32121. segment.stats = getRequestStats(request);
  32122. if (segment.key) {
  32123. segment.encryptedBytes = new Uint8Array(request.response);
  32124. } else {
  32125. segment.bytes = new Uint8Array(request.response);
  32126. } // This is likely an FMP4 and has the init segment.
  32127. // Run through the CaptionParser in case there are captions.
  32128. if (segment.map && segment.map.bytes) {
  32129. // Initialize CaptionParser if it hasn't been yet
  32130. if (!captionParser.isInitialized()) {
  32131. captionParser.init();
  32132. }
  32133. parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales);
  32134. if (parsed && parsed.captions) {
  32135. segment.captionStreams = parsed.captionStreams;
  32136. segment.fmp4Captions = parsed.captions;
  32137. }
  32138. }
  32139. return finishProcessingFn(null, segment);
  32140. };
  32141. };
  32142. /**
  32143. * Decrypt the segment via the decryption web worker
  32144. *
  32145. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  32146. * @param {Object} segment - a simplified copy of the segmentInfo object
  32147. * from SegmentLoader
  32148. * @param {Function} doneFn - a callback that is executed after decryption has completed
  32149. */
  32150. var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
  32151. var decryptionHandler = function decryptionHandler(event) {
  32152. if (event.data.source === segment.requestId) {
  32153. decrypter.removeEventListener('message', decryptionHandler);
  32154. var decrypted = event.data.decrypted;
  32155. segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
  32156. return doneFn(null, segment);
  32157. }
  32158. };
  32159. decrypter.addEventListener('message', decryptionHandler);
  32160. var keyBytes = segment.key.bytes.slice(); // this is an encrypted segment
  32161. // incrementally decrypt the segment
  32162. decrypter.postMessage(createTransferableMessage({
  32163. source: segment.requestId,
  32164. encrypted: segment.encryptedBytes,
  32165. key: keyBytes,
  32166. iv: segment.key.iv
  32167. }), [segment.encryptedBytes.buffer, keyBytes.buffer]);
  32168. };
  32169. /**
  32170. * This function waits for all XHRs to finish (with either success or failure)
  32171. * before continueing processing via it's callback. The function gathers errors
  32172. * from each request into a single errors array so that the error status for
  32173. * each request can be examined later.
  32174. *
  32175. * @param {Object} activeXhrs - an object that tracks all XHR requests
  32176. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  32177. * @param {Function} doneFn - a callback that is executed after all resources have been
  32178. * downloaded and any decryption completed
  32179. */
  32180. var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
  32181. var count = 0;
  32182. var didError = false;
  32183. return function (error, segment) {
  32184. if (didError) {
  32185. return;
  32186. }
  32187. if (error) {
  32188. didError = true; // If there are errors, we have to abort any outstanding requests
  32189. abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
  32190. // handle the aborted events from those requests, there are some cases where we may
  32191. // never get an aborted event. For instance, if the network connection is lost and
  32192. // there were two requests, the first may have triggered an error immediately, while
  32193. // the second request remains unsent. In that case, the aborted algorithm will not
  32194. // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
  32195. //
  32196. // We also can't rely on the ready state of the XHR, since the request that
  32197. // triggered the connection error may also show as a ready state of 0 (unsent).
  32198. // Therefore, we have to finish this group of requests immediately after the first
  32199. // seen error.
  32200. return doneFn(error, segment);
  32201. }
  32202. count += 1;
  32203. if (count === activeXhrs.length) {
  32204. // Keep track of when *all* of the requests have completed
  32205. segment.endOfAllRequests = Date.now();
  32206. if (segment.encryptedBytes) {
  32207. return decryptSegment(decrypter, segment, doneFn);
  32208. } // Otherwise, everything is ready just continue
  32209. return doneFn(null, segment);
  32210. }
  32211. };
  32212. };
  32213. /**
  32214. * Simple progress event callback handler that gathers some stats before
  32215. * executing a provided callback with the `segment` object
  32216. *
  32217. * @param {Object} segment - a simplified copy of the segmentInfo object
  32218. * from SegmentLoader
  32219. * @param {Function} progressFn - a callback that is executed each time a progress event
  32220. * is received
  32221. * @param {Event} event - the progress event object from XMLHttpRequest
  32222. */
  32223. var handleProgress = function handleProgress(segment, progressFn) {
  32224. return function (event) {
  32225. segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
  32226. if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
  32227. segment.stats.firstBytesReceivedAt = Date.now();
  32228. }
  32229. return progressFn(event, segment);
  32230. };
  32231. };
  32232. /**
  32233. * Load all resources and does any processing necessary for a media-segment
  32234. *
  32235. * Features:
  32236. * decrypts the media-segment if it has a key uri and an iv
  32237. * aborts *all* requests if *any* one request fails
  32238. *
  32239. * The segment object, at minimum, has the following format:
  32240. * {
  32241. * resolvedUri: String,
  32242. * [byterange]: {
  32243. * offset: Number,
  32244. * length: Number
  32245. * },
  32246. * [key]: {
  32247. * resolvedUri: String
  32248. * [byterange]: {
  32249. * offset: Number,
  32250. * length: Number
  32251. * },
  32252. * iv: {
  32253. * bytes: Uint32Array
  32254. * }
  32255. * },
  32256. * [map]: {
  32257. * resolvedUri: String,
  32258. * [byterange]: {
  32259. * offset: Number,
  32260. * length: Number
  32261. * },
  32262. * [bytes]: Uint8Array
  32263. * }
  32264. * }
  32265. * ...where [name] denotes optional properties
  32266. *
  32267. * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
  32268. * @param {Object} xhrOptions - the base options to provide to all xhr requests
  32269. * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
  32270. * decryption routines
  32271. * @param {Object} segment - a simplified copy of the segmentInfo object
  32272. * from SegmentLoader
  32273. * @param {Function} progressFn - a callback that receives progress events from the main
  32274. * segment's xhr request
  32275. * @param {Function} doneFn - a callback that is executed only once all requests have
  32276. * succeeded or failed
  32277. * @returns {Function} a function that, when invoked, immediately aborts all
  32278. * outstanding requests
  32279. */
  32280. var mediaSegmentRequest = function mediaSegmentRequest(xhr$$1, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) {
  32281. var activeXhrs = [];
  32282. var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key
  32283. if (segment.key && !segment.key.bytes) {
  32284. var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  32285. uri: segment.key.resolvedUri,
  32286. responseType: 'arraybuffer'
  32287. });
  32288. var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
  32289. var keyXhr = xhr$$1(keyRequestOptions, keyRequestCallback);
  32290. activeXhrs.push(keyXhr);
  32291. } // optionally, request the associated media init segment
  32292. if (segment.map && !segment.map.bytes) {
  32293. var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
  32294. uri: segment.map.resolvedUri,
  32295. responseType: 'arraybuffer',
  32296. headers: segmentXhrHeaders(segment.map)
  32297. });
  32298. var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn);
  32299. var initSegmentXhr = xhr$$1(initSegmentOptions, initSegmentRequestCallback);
  32300. activeXhrs.push(initSegmentXhr);
  32301. }
  32302. var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  32303. uri: segment.resolvedUri,
  32304. responseType: 'arraybuffer',
  32305. headers: segmentXhrHeaders(segment)
  32306. });
  32307. var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn);
  32308. var segmentXhr = xhr$$1(segmentRequestOptions, segmentRequestCallback);
  32309. segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
  32310. activeXhrs.push(segmentXhr);
  32311. return function () {
  32312. return abortAll(activeXhrs);
  32313. };
  32314. }; // Utilities
  32315. /**
  32316. * Returns the CSS value for the specified property on an element
  32317. * using `getComputedStyle`. Firefox has a long-standing issue where
  32318. * getComputedStyle() may return null when running in an iframe with
  32319. * `display: none`.
  32320. *
  32321. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  32322. * @param {HTMLElement} el the htmlelement to work on
  32323. * @param {string} the proprety to get the style for
  32324. */
  32325. var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
  32326. var result = void 0;
  32327. if (!el) {
  32328. return '';
  32329. }
  32330. result = window$1.getComputedStyle(el);
  32331. if (!result) {
  32332. return '';
  32333. }
  32334. return result[property];
  32335. };
  32336. /**
  32337. * Resuable stable sort function
  32338. *
  32339. * @param {Playlists} array
  32340. * @param {Function} sortFn Different comparators
  32341. * @function stableSort
  32342. */
  32343. var stableSort = function stableSort(array, sortFn) {
  32344. var newArray = array.slice();
  32345. array.sort(function (left, right) {
  32346. var cmp = sortFn(left, right);
  32347. if (cmp === 0) {
  32348. return newArray.indexOf(left) - newArray.indexOf(right);
  32349. }
  32350. return cmp;
  32351. });
  32352. };
  32353. /**
  32354. * A comparator function to sort two playlist object by bandwidth.
  32355. *
  32356. * @param {Object} left a media playlist object
  32357. * @param {Object} right a media playlist object
  32358. * @return {Number} Greater than zero if the bandwidth attribute of
  32359. * left is greater than the corresponding attribute of right. Less
  32360. * than zero if the bandwidth of right is greater than left and
  32361. * exactly zero if the two are equal.
  32362. */
  32363. var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
  32364. var leftBandwidth = void 0;
  32365. var rightBandwidth = void 0;
  32366. if (left.attributes.BANDWIDTH) {
  32367. leftBandwidth = left.attributes.BANDWIDTH;
  32368. }
  32369. leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
  32370. if (right.attributes.BANDWIDTH) {
  32371. rightBandwidth = right.attributes.BANDWIDTH;
  32372. }
  32373. rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
  32374. return leftBandwidth - rightBandwidth;
  32375. };
  32376. /**
  32377. * A comparator function to sort two playlist object by resolution (width).
  32378. * @param {Object} left a media playlist object
  32379. * @param {Object} right a media playlist object
  32380. * @return {Number} Greater than zero if the resolution.width attribute of
  32381. * left is greater than the corresponding attribute of right. Less
  32382. * than zero if the resolution.width of right is greater than left and
  32383. * exactly zero if the two are equal.
  32384. */
  32385. var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
  32386. var leftWidth = void 0;
  32387. var rightWidth = void 0;
  32388. if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
  32389. leftWidth = left.attributes.RESOLUTION.width;
  32390. }
  32391. leftWidth = leftWidth || window$1.Number.MAX_VALUE;
  32392. if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
  32393. rightWidth = right.attributes.RESOLUTION.width;
  32394. }
  32395. rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
  32396. // have the same media dimensions/ resolution
  32397. if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
  32398. return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
  32399. }
  32400. return leftWidth - rightWidth;
  32401. };
  32402. /**
  32403. * Chooses the appropriate media playlist based on bandwidth and player size
  32404. *
  32405. * @param {Object} master
  32406. * Object representation of the master manifest
  32407. * @param {Number} playerBandwidth
  32408. * Current calculated bandwidth of the player
  32409. * @param {Number} playerWidth
  32410. * Current width of the player element
  32411. * @param {Number} playerHeight
  32412. * Current height of the player element
  32413. * @param {Boolean} limitRenditionByPlayerDimensions
  32414. * True if the player width and height should be used during the selection, false otherwise
  32415. * @return {Playlist} the highest bitrate playlist less than the
  32416. * currently detected bandwidth, accounting for some amount of
  32417. * bandwidth variance
  32418. */
  32419. var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) {
  32420. // convert the playlists to an intermediary representation to make comparisons easier
  32421. var sortedPlaylistReps = master.playlists.map(function (playlist) {
  32422. var width = void 0;
  32423. var height = void 0;
  32424. var bandwidth = void 0;
  32425. width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
  32426. height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
  32427. bandwidth = playlist.attributes.BANDWIDTH;
  32428. bandwidth = bandwidth || window$1.Number.MAX_VALUE;
  32429. return {
  32430. bandwidth: bandwidth,
  32431. width: width,
  32432. height: height,
  32433. playlist: playlist
  32434. };
  32435. });
  32436. stableSort(sortedPlaylistReps, function (left, right) {
  32437. return left.bandwidth - right.bandwidth;
  32438. }); // filter out any playlists that have been excluded due to
  32439. // incompatible configurations
  32440. sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  32441. return !Playlist.isIncompatible(rep.playlist);
  32442. }); // filter out any playlists that have been disabled manually through the representations
  32443. // api or blacklisted temporarily due to playback errors.
  32444. var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  32445. return Playlist.isEnabled(rep.playlist);
  32446. });
  32447. if (!enabledPlaylistReps.length) {
  32448. // if there are no enabled playlists, then they have all been blacklisted or disabled
  32449. // by the user through the representations api. In this case, ignore blacklisting and
  32450. // fallback to what the user wants by using playlists the user has not disabled.
  32451. enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  32452. return !Playlist.isDisabled(rep.playlist);
  32453. });
  32454. } // filter out any variant that has greater effective bitrate
  32455. // than the current estimated bandwidth
  32456. var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
  32457. return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
  32458. });
  32459. var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
  32460. // and then taking the very first element
  32461. var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
  32462. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  32463. })[0]; // if we're not going to limit renditions by player size, make an early decision.
  32464. if (limitRenditionByPlayerDimensions === false) {
  32465. var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  32466. return _chosenRep ? _chosenRep.playlist : null;
  32467. } // filter out playlists without resolution information
  32468. var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
  32469. return rep.width && rep.height;
  32470. }); // sort variants by resolution
  32471. stableSort(haveResolution, function (left, right) {
  32472. return left.width - right.width;
  32473. }); // if we have the exact resolution as the player use it
  32474. var resolutionBestRepList = haveResolution.filter(function (rep) {
  32475. return rep.width === playerWidth && rep.height === playerHeight;
  32476. });
  32477. highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
  32478. var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
  32479. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  32480. })[0];
  32481. var resolutionPlusOneList = void 0;
  32482. var resolutionPlusOneSmallest = void 0;
  32483. var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player
  32484. // if there is no match of exact resolution
  32485. if (!resolutionBestRep) {
  32486. resolutionPlusOneList = haveResolution.filter(function (rep) {
  32487. return rep.width > playerWidth || rep.height > playerHeight;
  32488. }); // find all the variants have the same smallest resolution
  32489. resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
  32490. return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
  32491. }); // ensure that we also pick the highest bandwidth variant that
  32492. // is just-larger-than the video player
  32493. highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
  32494. resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
  32495. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  32496. })[0];
  32497. } // fallback chain of variants
  32498. var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  32499. return chosenRep ? chosenRep.playlist : null;
  32500. }; // Playlist Selectors
  32501. /**
  32502. * Chooses the appropriate media playlist based on the most recent
  32503. * bandwidth estimate and the player size.
  32504. *
  32505. * Expects to be called within the context of an instance of HlsHandler
  32506. *
  32507. * @return {Playlist} the highest bitrate playlist less than the
  32508. * currently detected bandwidth, accounting for some amount of
  32509. * bandwidth variance
  32510. */
  32511. var lastBandwidthSelector = function lastBandwidthSelector() {
  32512. return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions);
  32513. };
  32514. /**
  32515. * Chooses the appropriate media playlist based on the potential to rebuffer
  32516. *
  32517. * @param {Object} settings
  32518. * Object of information required to use this selector
  32519. * @param {Object} settings.master
  32520. * Object representation of the master manifest
  32521. * @param {Number} settings.currentTime
  32522. * The current time of the player
  32523. * @param {Number} settings.bandwidth
  32524. * Current measured bandwidth
  32525. * @param {Number} settings.duration
  32526. * Duration of the media
  32527. * @param {Number} settings.segmentDuration
  32528. * Segment duration to be used in round trip time calculations
  32529. * @param {Number} settings.timeUntilRebuffer
  32530. * Time left in seconds until the player has to rebuffer
  32531. * @param {Number} settings.currentTimeline
  32532. * The current timeline segments are being loaded from
  32533. * @param {SyncController} settings.syncController
  32534. * SyncController for determining if we have a sync point for a given playlist
  32535. * @return {Object|null}
  32536. * {Object} return.playlist
  32537. * The highest bandwidth playlist with the least amount of rebuffering
  32538. * {Number} return.rebufferingImpact
  32539. * The amount of time in seconds switching to this playlist will rebuffer. A
  32540. * negative value means that switching will cause zero rebuffering.
  32541. */
  32542. var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
  32543. var master = settings.master,
  32544. currentTime = settings.currentTime,
  32545. bandwidth = settings.bandwidth,
  32546. duration$$1 = settings.duration,
  32547. segmentDuration = settings.segmentDuration,
  32548. timeUntilRebuffer = settings.timeUntilRebuffer,
  32549. currentTimeline = settings.currentTimeline,
  32550. syncController = settings.syncController; // filter out any playlists that have been excluded due to
  32551. // incompatible configurations
  32552. var compatiblePlaylists = master.playlists.filter(function (playlist) {
  32553. return !Playlist.isIncompatible(playlist);
  32554. }); // filter out any playlists that have been disabled manually through the representations
  32555. // api or blacklisted temporarily due to playback errors.
  32556. var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
  32557. if (!enabledPlaylists.length) {
  32558. // if there are no enabled playlists, then they have all been blacklisted or disabled
  32559. // by the user through the representations api. In this case, ignore blacklisting and
  32560. // fallback to what the user wants by using playlists the user has not disabled.
  32561. enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
  32562. return !Playlist.isDisabled(playlist);
  32563. });
  32564. }
  32565. var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
  32566. var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
  32567. var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
  32568. // sync request first. This will double the request time
  32569. var numRequests = syncPoint ? 1 : 2;
  32570. var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
  32571. var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
  32572. return {
  32573. playlist: playlist,
  32574. rebufferingImpact: rebufferingImpact
  32575. };
  32576. });
  32577. var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
  32578. return estimate.rebufferingImpact <= 0;
  32579. }); // Sort by bandwidth DESC
  32580. stableSort(noRebufferingPlaylists, function (a, b) {
  32581. return comparePlaylistBandwidth(b.playlist, a.playlist);
  32582. });
  32583. if (noRebufferingPlaylists.length) {
  32584. return noRebufferingPlaylists[0];
  32585. }
  32586. stableSort(rebufferingEstimates, function (a, b) {
  32587. return a.rebufferingImpact - b.rebufferingImpact;
  32588. });
  32589. return rebufferingEstimates[0] || null;
  32590. };
  32591. /**
  32592. * Chooses the appropriate media playlist, which in this case is the lowest bitrate
  32593. * one with video. If no renditions with video exist, return the lowest audio rendition.
  32594. *
  32595. * Expects to be called within the context of an instance of HlsHandler
  32596. *
  32597. * @return {Object|null}
  32598. * {Object} return.playlist
  32599. * The lowest bitrate playlist that contains a video codec. If no such rendition
  32600. * exists pick the lowest audio rendition.
  32601. */
  32602. var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
  32603. // filter out any playlists that have been excluded due to
  32604. // incompatible configurations or playback errors
  32605. var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
  32606. stableSort(playlists, function (a, b) {
  32607. return comparePlaylistBandwidth(a, b);
  32608. }); // Parse and assume that playlists with no video codec have no video
  32609. // (this is not necessarily true, although it is generally true).
  32610. //
  32611. // If an entire manifest has no valid videos everything will get filtered
  32612. // out.
  32613. var playlistsWithVideo = playlists.filter(function (playlist) {
  32614. return parseCodecs(playlist.attributes.CODECS).videoCodec;
  32615. });
  32616. return playlistsWithVideo[0] || null;
  32617. };
  32618. /**
  32619. * Create captions text tracks on video.js if they do not exist
  32620. *
  32621. * @param {Object} inbandTextTracks a reference to current inbandTextTracks
  32622. * @param {Object} tech the video.js tech
  32623. * @param {Object} captionStreams the caption streams to create
  32624. * @private
  32625. */
  32626. var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) {
  32627. for (var trackId in captionStreams) {
  32628. if (!inbandTextTracks[trackId]) {
  32629. tech.trigger({
  32630. type: 'usage',
  32631. name: 'hls-608'
  32632. });
  32633. var track = tech.textTracks().getTrackById(trackId);
  32634. if (track) {
  32635. // Resuse an existing track with a CC# id because this was
  32636. // very likely created by videojs-contrib-hls from information
  32637. // in the m3u8 for us to use
  32638. inbandTextTracks[trackId] = track;
  32639. } else {
  32640. // Otherwise, create a track with the default `CC#` label and
  32641. // without a language
  32642. inbandTextTracks[trackId] = tech.addRemoteTextTrack({
  32643. kind: 'captions',
  32644. id: trackId,
  32645. label: trackId
  32646. }, false).track;
  32647. }
  32648. }
  32649. }
  32650. };
  32651. var addCaptionData = function addCaptionData(_ref) {
  32652. var inbandTextTracks = _ref.inbandTextTracks,
  32653. captionArray = _ref.captionArray,
  32654. timestampOffset = _ref.timestampOffset;
  32655. if (!captionArray) {
  32656. return;
  32657. }
  32658. var Cue = window.WebKitDataCue || window.VTTCue;
  32659. captionArray.forEach(function (caption) {
  32660. var track = caption.stream;
  32661. var startTime = caption.startTime;
  32662. var endTime = caption.endTime;
  32663. if (!inbandTextTracks[track]) {
  32664. return;
  32665. }
  32666. startTime += timestampOffset;
  32667. endTime += timestampOffset;
  32668. inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text));
  32669. });
  32670. };
  32671. /**
  32672. * @file segment-loader.js
  32673. */
  32674. // in ms
  32675. var CHECK_BUFFER_DELAY = 500;
  32676. /**
  32677. * Determines if we should call endOfStream on the media source based
  32678. * on the state of the buffer or if appened segment was the final
  32679. * segment in the playlist.
  32680. *
  32681. * @param {Object} playlist a media playlist object
  32682. * @param {Object} mediaSource the MediaSource object
  32683. * @param {Number} segmentIndex the index of segment we last appended
  32684. * @returns {Boolean} do we need to call endOfStream on the MediaSource
  32685. */
  32686. var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
  32687. if (!playlist || !mediaSource) {
  32688. return false;
  32689. }
  32690. var segments = playlist.segments; // determine a few boolean values to help make the branch below easier
  32691. // to read
  32692. var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream
  32693. // so that MediaSources can trigger the `ended` event when it runs out of
  32694. // buffered data instead of waiting for me
  32695. return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
  32696. };
  32697. var finite = function finite(num) {
  32698. return typeof num === 'number' && isFinite(num);
  32699. };
  32700. var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
  32701. // Although these checks should most likely cover non 'main' types, for now it narrows
  32702. // the scope of our checks.
  32703. if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
  32704. return null;
  32705. }
  32706. if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
  32707. return 'Neither audio nor video found in segment.';
  32708. }
  32709. if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
  32710. 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.';
  32711. }
  32712. if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
  32713. 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.';
  32714. }
  32715. return null;
  32716. };
  32717. /**
  32718. * Calculates a time value that is safe to remove from the back buffer without interupting
  32719. * playback.
  32720. *
  32721. * @param {TimeRange} seekable
  32722. * The current seekable range
  32723. * @param {Number} currentTime
  32724. * The current time of the player
  32725. * @param {Number} targetDuration
  32726. * The target duration of the current playlist
  32727. * @return {Number}
  32728. * Time that is safe to remove from the back buffer without interupting playback
  32729. */
  32730. var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
  32731. var removeToTime = void 0;
  32732. if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
  32733. // If we have a seekable range use that as the limit for what can be removed safely
  32734. removeToTime = seekable$$1.start(0);
  32735. } else {
  32736. // otherwise remove anything older than 30 seconds before the current play head
  32737. removeToTime = currentTime - 30;
  32738. } // Don't allow removing from the buffer within target duration of current time
  32739. // to avoid the possibility of removing the GOP currently being played which could
  32740. // cause playback stalls.
  32741. return Math.min(removeToTime, currentTime - targetDuration);
  32742. };
  32743. var segmentInfoString = function segmentInfoString(segmentInfo) {
  32744. var _segmentInfo$segment = segmentInfo.segment,
  32745. start = _segmentInfo$segment.start,
  32746. end = _segmentInfo$segment.end,
  32747. _segmentInfo$playlist = segmentInfo.playlist,
  32748. seq = _segmentInfo$playlist.mediaSequence,
  32749. id = _segmentInfo$playlist.id,
  32750. _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
  32751. segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
  32752. index = segmentInfo.mediaIndex,
  32753. timeline = segmentInfo.timeline;
  32754. return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
  32755. };
  32756. /**
  32757. * An object that manages segment loading and appending.
  32758. *
  32759. * @class SegmentLoader
  32760. * @param {Object} options required and optional options
  32761. * @extends videojs.EventTarget
  32762. */
  32763. var SegmentLoader = function (_videojs$EventTarget) {
  32764. inherits(SegmentLoader, _videojs$EventTarget);
  32765. function SegmentLoader(settings) {
  32766. classCallCheck(this, SegmentLoader); // check pre-conditions
  32767. var _this = possibleConstructorReturn(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
  32768. if (!settings) {
  32769. throw new TypeError('Initialization settings are required');
  32770. }
  32771. if (typeof settings.currentTime !== 'function') {
  32772. throw new TypeError('No currentTime getter specified');
  32773. }
  32774. if (!settings.mediaSource) {
  32775. throw new TypeError('No MediaSource specified');
  32776. } // public properties
  32777. _this.bandwidth = settings.bandwidth;
  32778. _this.throughput = {
  32779. rate: 0,
  32780. count: 0
  32781. };
  32782. _this.roundTrip = NaN;
  32783. _this.resetStats_();
  32784. _this.mediaIndex = null; // private settings
  32785. _this.hasPlayed_ = settings.hasPlayed;
  32786. _this.currentTime_ = settings.currentTime;
  32787. _this.seekable_ = settings.seekable;
  32788. _this.seeking_ = settings.seeking;
  32789. _this.duration_ = settings.duration;
  32790. _this.mediaSource_ = settings.mediaSource;
  32791. _this.hls_ = settings.hls;
  32792. _this.loaderType_ = settings.loaderType;
  32793. _this.startingMedia_ = void 0;
  32794. _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
  32795. _this.goalBufferLength_ = settings.goalBufferLength;
  32796. _this.sourceType_ = settings.sourceType;
  32797. _this.inbandTextTracks_ = settings.inbandTextTracks;
  32798. _this.state_ = 'INIT'; // private instance variables
  32799. _this.checkBufferTimeout_ = null;
  32800. _this.error_ = void 0;
  32801. _this.currentTimeline_ = -1;
  32802. _this.pendingSegment_ = null;
  32803. _this.mimeType_ = null;
  32804. _this.sourceUpdater_ = null;
  32805. _this.xhrOptions_ = null; // Fragmented mp4 playback
  32806. _this.activeInitSegmentId_ = null;
  32807. _this.initSegments_ = {}; // HLSe playback
  32808. _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
  32809. _this.keyCache_ = {}; // Fmp4 CaptionParser
  32810. _this.captionParser_ = new mp4.CaptionParser();
  32811. _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
  32812. // between a time in the display time and a segment index within
  32813. // a playlist
  32814. _this.syncController_ = settings.syncController;
  32815. _this.syncPoint_ = {
  32816. segmentIndex: 0,
  32817. time: 0
  32818. };
  32819. _this.syncController_.on('syncinfoupdate', function () {
  32820. return _this.trigger('syncinfoupdate');
  32821. });
  32822. _this.mediaSource_.addEventListener('sourceopen', function () {
  32823. return _this.ended_ = false;
  32824. }); // ...for determining the fetch location
  32825. _this.fetchAtBuffer_ = false;
  32826. _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
  32827. Object.defineProperty(_this, 'state', {
  32828. get: function get$$1() {
  32829. return this.state_;
  32830. },
  32831. set: function set$$1(newState) {
  32832. if (newState !== this.state_) {
  32833. this.logger_(this.state_ + ' -> ' + newState);
  32834. this.state_ = newState;
  32835. }
  32836. }
  32837. });
  32838. return _this;
  32839. }
  32840. /**
  32841. * reset all of our media stats
  32842. *
  32843. * @private
  32844. */
  32845. createClass(SegmentLoader, [{
  32846. key: 'resetStats_',
  32847. value: function resetStats_() {
  32848. this.mediaBytesTransferred = 0;
  32849. this.mediaRequests = 0;
  32850. this.mediaRequestsAborted = 0;
  32851. this.mediaRequestsTimedout = 0;
  32852. this.mediaRequestsErrored = 0;
  32853. this.mediaTransferDuration = 0;
  32854. this.mediaSecondsLoaded = 0;
  32855. }
  32856. /**
  32857. * dispose of the SegmentLoader and reset to the default state
  32858. */
  32859. }, {
  32860. key: 'dispose',
  32861. value: function dispose() {
  32862. this.state = 'DISPOSED';
  32863. this.pause();
  32864. this.abort_();
  32865. if (this.sourceUpdater_) {
  32866. this.sourceUpdater_.dispose();
  32867. }
  32868. this.resetStats_();
  32869. this.captionParser_.reset();
  32870. }
  32871. /**
  32872. * abort anything that is currently doing on with the SegmentLoader
  32873. * and reset to a default state
  32874. */
  32875. }, {
  32876. key: 'abort',
  32877. value: function abort() {
  32878. if (this.state !== 'WAITING') {
  32879. if (this.pendingSegment_) {
  32880. this.pendingSegment_ = null;
  32881. }
  32882. return;
  32883. }
  32884. this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
  32885. // since we are no longer "waiting" on any requests. XHR callback is not always run
  32886. // when the request is aborted. This will prevent the loader from being stuck in the
  32887. // WAITING state indefinitely.
  32888. this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
  32889. // next segment
  32890. if (!this.paused()) {
  32891. this.monitorBuffer_();
  32892. }
  32893. }
  32894. /**
  32895. * abort all pending xhr requests and null any pending segements
  32896. *
  32897. * @private
  32898. */
  32899. }, {
  32900. key: 'abort_',
  32901. value: function abort_() {
  32902. if (this.pendingSegment_) {
  32903. this.pendingSegment_.abortRequests();
  32904. } // clear out the segment being processed
  32905. this.pendingSegment_ = null;
  32906. }
  32907. /**
  32908. * set an error on the segment loader and null out any pending segements
  32909. *
  32910. * @param {Error} error the error to set on the SegmentLoader
  32911. * @return {Error} the error that was set or that is currently set
  32912. */
  32913. }, {
  32914. key: 'error',
  32915. value: function error(_error) {
  32916. if (typeof _error !== 'undefined') {
  32917. this.error_ = _error;
  32918. }
  32919. this.pendingSegment_ = null;
  32920. return this.error_;
  32921. }
  32922. }, {
  32923. key: 'endOfStream',
  32924. value: function endOfStream() {
  32925. this.ended_ = true;
  32926. this.pause();
  32927. this.trigger('ended');
  32928. }
  32929. /**
  32930. * Indicates which time ranges are buffered
  32931. *
  32932. * @return {TimeRange}
  32933. * TimeRange object representing the current buffered ranges
  32934. */
  32935. }, {
  32936. key: 'buffered_',
  32937. value: function buffered_() {
  32938. if (!this.sourceUpdater_) {
  32939. return videojs$1.createTimeRanges();
  32940. }
  32941. return this.sourceUpdater_.buffered();
  32942. }
  32943. /**
  32944. * Gets and sets init segment for the provided map
  32945. *
  32946. * @param {Object} map
  32947. * The map object representing the init segment to get or set
  32948. * @param {Boolean=} set
  32949. * If true, the init segment for the provided map should be saved
  32950. * @return {Object}
  32951. * map object for desired init segment
  32952. */
  32953. }, {
  32954. key: 'initSegment',
  32955. value: function initSegment(map) {
  32956. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  32957. if (!map) {
  32958. return null;
  32959. }
  32960. var id = initSegmentId(map);
  32961. var storedMap = this.initSegments_[id];
  32962. if (set$$1 && !storedMap && map.bytes) {
  32963. this.initSegments_[id] = storedMap = {
  32964. resolvedUri: map.resolvedUri,
  32965. byterange: map.byterange,
  32966. bytes: map.bytes,
  32967. timescales: map.timescales,
  32968. videoTrackIds: map.videoTrackIds
  32969. };
  32970. }
  32971. return storedMap || map;
  32972. }
  32973. /**
  32974. * Gets and sets key for the provided key
  32975. *
  32976. * @param {Object} key
  32977. * The key object representing the key to get or set
  32978. * @param {Boolean=} set
  32979. * If true, the key for the provided key should be saved
  32980. * @return {Object}
  32981. * Key object for desired key
  32982. */
  32983. }, {
  32984. key: 'segmentKey',
  32985. value: function segmentKey(key) {
  32986. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  32987. if (!key) {
  32988. return null;
  32989. }
  32990. var id = segmentKeyId(key);
  32991. var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
  32992. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
  32993. if (this.cacheEncryptionKeys_ && set$$1 && !storedKey && key.bytes) {
  32994. this.keyCache_[id] = storedKey = {
  32995. resolvedUri: key.resolvedUri,
  32996. bytes: key.bytes
  32997. };
  32998. }
  32999. var result = {
  33000. resolvedUri: (storedKey || key).resolvedUri
  33001. };
  33002. if (storedKey) {
  33003. result.bytes = storedKey.bytes;
  33004. }
  33005. return result;
  33006. }
  33007. /**
  33008. * Returns true if all configuration required for loading is present, otherwise false.
  33009. *
  33010. * @return {Boolean} True if the all configuration is ready for loading
  33011. * @private
  33012. */
  33013. }, {
  33014. key: 'couldBeginLoading_',
  33015. value: function couldBeginLoading_() {
  33016. return this.playlist_ && ( // the source updater is created when init_ is called, so either having a
  33017. // source updater or being in the INIT state with a mimeType is enough
  33018. // to say we have all the needed configuration to start loading.
  33019. this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
  33020. }
  33021. /**
  33022. * load a playlist and start to fill the buffer
  33023. */
  33024. }, {
  33025. key: 'load',
  33026. value: function load() {
  33027. // un-pause
  33028. this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
  33029. // specified
  33030. if (!this.playlist_) {
  33031. return;
  33032. } // not sure if this is the best place for this
  33033. this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading
  33034. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  33035. return this.init_();
  33036. } // if we're in the middle of processing a segment already, don't
  33037. // kick off an additional segment request
  33038. if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
  33039. return;
  33040. }
  33041. this.state = 'READY';
  33042. }
  33043. /**
  33044. * Once all the starting parameters have been specified, begin
  33045. * operation. This method should only be invoked from the INIT
  33046. * state.
  33047. *
  33048. * @private
  33049. */
  33050. }, {
  33051. key: 'init_',
  33052. value: function init_() {
  33053. this.state = 'READY';
  33054. this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
  33055. this.resetEverything();
  33056. return this.monitorBuffer_();
  33057. }
  33058. /**
  33059. * set a playlist on the segment loader
  33060. *
  33061. * @param {PlaylistLoader} media the playlist to set on the segment loader
  33062. */
  33063. }, {
  33064. key: 'playlist',
  33065. value: function playlist(newPlaylist) {
  33066. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  33067. if (!newPlaylist) {
  33068. return;
  33069. }
  33070. var oldPlaylist = this.playlist_;
  33071. var segmentInfo = this.pendingSegment_;
  33072. this.playlist_ = newPlaylist;
  33073. this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
  33074. // is always our zero-time so force a sync update each time the playlist
  33075. // is refreshed from the server
  33076. if (!this.hasPlayed_()) {
  33077. newPlaylist.syncInfo = {
  33078. mediaSequence: newPlaylist.mediaSequence,
  33079. time: 0
  33080. };
  33081. }
  33082. var oldId = null;
  33083. if (oldPlaylist) {
  33084. if (oldPlaylist.id) {
  33085. oldId = oldPlaylist.id;
  33086. } else if (oldPlaylist.uri) {
  33087. oldId = oldPlaylist.uri;
  33088. }
  33089. }
  33090. this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
  33091. // in LIVE, we always want to update with new playlists (including refreshes)
  33092. this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
  33093. // buffering now
  33094. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  33095. return this.init_();
  33096. }
  33097. if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
  33098. if (this.mediaIndex !== null) {
  33099. // we must "resync" the segment loader when we switch renditions and
  33100. // the segment loader is already synced to the previous rendition
  33101. this.resyncLoader();
  33102. } // the rest of this function depends on `oldPlaylist` being defined
  33103. return;
  33104. } // we reloaded the same playlist so we are in a live scenario
  33105. // and we will likely need to adjust the mediaIndex
  33106. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
  33107. this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader
  33108. // this is important because we can abort a request and this value must be
  33109. // equal to the last appended mediaIndex
  33110. if (this.mediaIndex !== null) {
  33111. this.mediaIndex -= mediaSequenceDiff;
  33112. } // update the mediaIndex on the SegmentInfo object
  33113. // this is important because we will update this.mediaIndex with this value
  33114. // in `handleUpdateEnd_` after the segment has been successfully appended
  33115. if (segmentInfo) {
  33116. segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is
  33117. // saved for the new playlist's segment, however, if the segment fell off the
  33118. // playlist, we can leave the old reference and just lose the timing info
  33119. if (segmentInfo.mediaIndex >= 0) {
  33120. segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
  33121. }
  33122. }
  33123. this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
  33124. }
  33125. /**
  33126. * Prevent the loader from fetching additional segments. If there
  33127. * is a segment request outstanding, it will finish processing
  33128. * before the loader halts. A segment loader can be unpaused by
  33129. * calling load().
  33130. */
  33131. }, {
  33132. key: 'pause',
  33133. value: function pause() {
  33134. if (this.checkBufferTimeout_) {
  33135. window$1.clearTimeout(this.checkBufferTimeout_);
  33136. this.checkBufferTimeout_ = null;
  33137. }
  33138. }
  33139. /**
  33140. * Returns whether the segment loader is fetching additional
  33141. * segments when given the opportunity. This property can be
  33142. * modified through calls to pause() and load().
  33143. */
  33144. }, {
  33145. key: 'paused',
  33146. value: function paused() {
  33147. return this.checkBufferTimeout_ === null;
  33148. }
  33149. /**
  33150. * create/set the following mimetype on the SourceBuffer through a
  33151. * SourceUpdater
  33152. *
  33153. * @param {String} mimeType the mime type string to use
  33154. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
  33155. * is added to the media source
  33156. */
  33157. }, {
  33158. key: 'mimeType',
  33159. value: function mimeType(_mimeType, sourceBufferEmitter) {
  33160. if (this.mimeType_) {
  33161. return;
  33162. }
  33163. this.mimeType_ = _mimeType;
  33164. this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start
  33165. // buffering now
  33166. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  33167. this.init_();
  33168. }
  33169. }
  33170. /**
  33171. * Delete all the buffered data and reset the SegmentLoader
  33172. * @param {Function} [done] an optional callback to be executed when the remove
  33173. * operation is complete
  33174. */
  33175. }, {
  33176. key: 'resetEverything',
  33177. value: function resetEverything(done) {
  33178. this.ended_ = false;
  33179. this.resetLoader();
  33180. this.remove(0, this.duration_(), done); // clears fmp4 captions
  33181. this.captionParser_.clearAllCaptions();
  33182. this.trigger('reseteverything');
  33183. }
  33184. /**
  33185. * Force the SegmentLoader to resync and start loading around the currentTime instead
  33186. * of starting at the end of the buffer
  33187. *
  33188. * Useful for fast quality changes
  33189. */
  33190. }, {
  33191. key: 'resetLoader',
  33192. value: function resetLoader() {
  33193. this.fetchAtBuffer_ = false;
  33194. this.resyncLoader();
  33195. }
  33196. /**
  33197. * Force the SegmentLoader to restart synchronization and make a conservative guess
  33198. * before returning to the simple walk-forward method
  33199. */
  33200. }, {
  33201. key: 'resyncLoader',
  33202. value: function resyncLoader() {
  33203. this.mediaIndex = null;
  33204. this.syncPoint_ = null;
  33205. this.abort();
  33206. }
  33207. /**
  33208. * Remove any data in the source buffer between start and end times
  33209. * @param {Number} start - the start time of the region to remove from the buffer
  33210. * @param {Number} end - the end time of the region to remove from the buffer
  33211. * @param {Function} [done] - an optional callback to be executed when the remove
  33212. * operation is complete
  33213. */
  33214. }, {
  33215. key: 'remove',
  33216. value: function remove(start, end, done) {
  33217. if (this.sourceUpdater_) {
  33218. this.sourceUpdater_.remove(start, end, done);
  33219. }
  33220. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  33221. if (this.inbandTextTracks_) {
  33222. for (var id in this.inbandTextTracks_) {
  33223. removeCuesFromTrack(start, end, this.inbandTextTracks_[id]);
  33224. }
  33225. }
  33226. }
  33227. /**
  33228. * (re-)schedule monitorBufferTick_ to run as soon as possible
  33229. *
  33230. * @private
  33231. */
  33232. }, {
  33233. key: 'monitorBuffer_',
  33234. value: function monitorBuffer_() {
  33235. if (this.checkBufferTimeout_) {
  33236. window$1.clearTimeout(this.checkBufferTimeout_);
  33237. }
  33238. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
  33239. }
  33240. /**
  33241. * As long as the SegmentLoader is in the READY state, periodically
  33242. * invoke fillBuffer_().
  33243. *
  33244. * @private
  33245. */
  33246. }, {
  33247. key: 'monitorBufferTick_',
  33248. value: function monitorBufferTick_() {
  33249. if (this.state === 'READY') {
  33250. this.fillBuffer_();
  33251. }
  33252. if (this.checkBufferTimeout_) {
  33253. window$1.clearTimeout(this.checkBufferTimeout_);
  33254. }
  33255. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
  33256. }
  33257. /**
  33258. * fill the buffer with segements unless the sourceBuffers are
  33259. * currently updating
  33260. *
  33261. * Note: this function should only ever be called by monitorBuffer_
  33262. * and never directly
  33263. *
  33264. * @private
  33265. */
  33266. }, {
  33267. key: 'fillBuffer_',
  33268. value: function fillBuffer_() {
  33269. if (this.sourceUpdater_.updating()) {
  33270. return;
  33271. }
  33272. if (!this.syncPoint_) {
  33273. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  33274. } // see if we need to begin loading immediately
  33275. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  33276. if (!segmentInfo) {
  33277. return;
  33278. }
  33279. if (this.isEndOfStream_(segmentInfo.mediaIndex)) {
  33280. this.endOfStream();
  33281. return;
  33282. }
  33283. if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
  33284. return;
  33285. } // We will need to change timestampOffset of the sourceBuffer if either of
  33286. // the following conditions are true:
  33287. // - The segment.timeline !== this.currentTimeline
  33288. // (we are crossing a discontinuity somehow)
  33289. // - The "timestampOffset" for the start of this segment is less than
  33290. // the currently set timestampOffset
  33291. // Also, clear captions if we are crossing a discontinuity boundary
  33292. if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) {
  33293. this.syncController_.reset();
  33294. segmentInfo.timestampOffset = segmentInfo.startOfSegment;
  33295. this.captionParser_.clearAllCaptions();
  33296. }
  33297. this.loadSegment_(segmentInfo);
  33298. }
  33299. /**
  33300. * Determines if this segment loader is at the end of it's stream.
  33301. *
  33302. * @param {Number} mediaIndex the index of segment we last appended
  33303. * @param {Object} [playlist=this.playlist_] a media playlist object
  33304. * @returns {Boolean} true if at end of stream, false otherwise.
  33305. */
  33306. }, {
  33307. key: 'isEndOfStream_',
  33308. value: function isEndOfStream_(mediaIndex) {
  33309. var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_;
  33310. return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating();
  33311. }
  33312. /**
  33313. * Determines what segment request should be made, given current playback
  33314. * state.
  33315. *
  33316. * @param {TimeRanges} buffered - the state of the buffer
  33317. * @param {Object} playlist - the playlist object to fetch segments from
  33318. * @param {Number} mediaIndex - the previous mediaIndex fetched or null
  33319. * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
  33320. * @param {Number} currentTime - the playback position in seconds
  33321. * @param {Object} syncPoint - a segment info object that describes the
  33322. * @returns {Object} a segment request object that describes the segment to load
  33323. */
  33324. }, {
  33325. key: 'checkBuffer_',
  33326. value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
  33327. var lastBufferedEnd = 0;
  33328. var startOfSegment = void 0;
  33329. if (buffered.length) {
  33330. lastBufferedEnd = buffered.end(buffered.length - 1);
  33331. }
  33332. var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
  33333. if (!playlist.segments.length) {
  33334. return null;
  33335. } // if there is plenty of content buffered, and the video has
  33336. // been played before relax for awhile
  33337. if (bufferedTime >= this.goalBufferLength_()) {
  33338. return null;
  33339. } // if the video has not yet played once, and we already have
  33340. // one segment downloaded do nothing
  33341. if (!hasPlayed && bufferedTime >= 1) {
  33342. return null;
  33343. } // When the syncPoint is null, there is no way of determining a good
  33344. // conservative segment index to fetch from
  33345. // The best thing to do here is to get the kind of sync-point data by
  33346. // making a request
  33347. if (syncPoint === null) {
  33348. mediaIndex = this.getSyncSegmentCandidate_(playlist);
  33349. return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
  33350. } // Under normal playback conditions fetching is a simple walk forward
  33351. if (mediaIndex !== null) {
  33352. var segment = playlist.segments[mediaIndex];
  33353. if (segment && segment.end) {
  33354. startOfSegment = segment.end;
  33355. } else {
  33356. startOfSegment = lastBufferedEnd;
  33357. }
  33358. return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
  33359. } // There is a sync-point but the lack of a mediaIndex indicates that
  33360. // we need to make a good conservative guess about which segment to
  33361. // fetch
  33362. if (this.fetchAtBuffer_) {
  33363. // Find the segment containing the end of the buffer
  33364. var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
  33365. mediaIndex = mediaSourceInfo.mediaIndex;
  33366. startOfSegment = mediaSourceInfo.startTime;
  33367. } else {
  33368. // Find the segment containing currentTime
  33369. var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
  33370. mediaIndex = _mediaSourceInfo.mediaIndex;
  33371. startOfSegment = _mediaSourceInfo.startTime;
  33372. }
  33373. return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
  33374. }
  33375. /**
  33376. * The segment loader has no recourse except to fetch a segment in the
  33377. * current playlist and use the internal timestamps in that segment to
  33378. * generate a syncPoint. This function returns a good candidate index
  33379. * for that process.
  33380. *
  33381. * @param {Object} playlist - the playlist object to look for a
  33382. * @returns {Number} An index of a segment from the playlist to load
  33383. */
  33384. }, {
  33385. key: 'getSyncSegmentCandidate_',
  33386. value: function getSyncSegmentCandidate_(playlist) {
  33387. var _this2 = this;
  33388. if (this.currentTimeline_ === -1) {
  33389. return 0;
  33390. }
  33391. var segmentIndexArray = playlist.segments.map(function (s, i) {
  33392. return {
  33393. timeline: s.timeline,
  33394. segmentIndex: i
  33395. };
  33396. }).filter(function (s) {
  33397. return s.timeline === _this2.currentTimeline_;
  33398. });
  33399. if (segmentIndexArray.length) {
  33400. return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
  33401. }
  33402. return Math.max(playlist.segments.length - 1, 0);
  33403. }
  33404. }, {
  33405. key: 'generateSegmentInfo_',
  33406. value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
  33407. if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
  33408. return null;
  33409. }
  33410. var segment = playlist.segments[mediaIndex];
  33411. return {
  33412. requestId: 'segment-loader-' + Math.random(),
  33413. // resolve the segment URL relative to the playlist
  33414. uri: segment.resolvedUri,
  33415. // the segment's mediaIndex at the time it was requested
  33416. mediaIndex: mediaIndex,
  33417. // whether or not to update the SegmentLoader's state with this
  33418. // segment's mediaIndex
  33419. isSyncRequest: isSyncRequest,
  33420. startOfSegment: startOfSegment,
  33421. // the segment's playlist
  33422. playlist: playlist,
  33423. // unencrypted bytes of the segment
  33424. bytes: null,
  33425. // when a key is defined for this segment, the encrypted bytes
  33426. encryptedBytes: null,
  33427. // The target timestampOffset for this segment when we append it
  33428. // to the source buffer
  33429. timestampOffset: null,
  33430. // The timeline that the segment is in
  33431. timeline: segment.timeline,
  33432. // The expected duration of the segment in seconds
  33433. duration: segment.duration,
  33434. // retain the segment in case the playlist updates while doing an async process
  33435. segment: segment
  33436. };
  33437. }
  33438. /**
  33439. * Determines if the network has enough bandwidth to complete the current segment
  33440. * request in a timely manner. If not, the request will be aborted early and bandwidth
  33441. * updated to trigger a playlist switch.
  33442. *
  33443. * @param {Object} stats
  33444. * Object containing stats about the request timing and size
  33445. * @return {Boolean} True if the request was aborted, false otherwise
  33446. * @private
  33447. */
  33448. }, {
  33449. key: 'abortRequestEarly_',
  33450. value: function abortRequestEarly_(stats) {
  33451. if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
  33452. // TODO: Replace using timeout with a boolean indicating whether this playlist is
  33453. // the lowestEnabledRendition.
  33454. !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
  33455. !this.playlist_.attributes.BANDWIDTH) {
  33456. return false;
  33457. } // Wait at least 1 second since the first byte of data has been received before
  33458. // using the calculated bandwidth from the progress event to allow the bitrate
  33459. // to stabilize
  33460. if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
  33461. return false;
  33462. }
  33463. var currentTime = this.currentTime_();
  33464. var measuredBandwidth = stats.bandwidth;
  33465. var segmentDuration = this.pendingSegment_.duration;
  33466. var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
  33467. // if we are only left with less than 1 second when the request completes.
  33468. // A negative timeUntilRebuffering indicates we are already rebuffering
  33469. var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
  33470. // is larger than the estimated time until the player runs out of forward buffer
  33471. if (requestTimeRemaining <= timeUntilRebuffer$$1) {
  33472. return false;
  33473. }
  33474. var switchCandidate = minRebufferMaxBandwidthSelector({
  33475. master: this.hls_.playlists.master,
  33476. currentTime: currentTime,
  33477. bandwidth: measuredBandwidth,
  33478. duration: this.duration_(),
  33479. segmentDuration: segmentDuration,
  33480. timeUntilRebuffer: timeUntilRebuffer$$1,
  33481. currentTimeline: this.currentTimeline_,
  33482. syncController: this.syncController_
  33483. });
  33484. if (!switchCandidate) {
  33485. return;
  33486. }
  33487. var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
  33488. var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
  33489. var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
  33490. // potential round trip time of the new request so that we are not too aggressive
  33491. // with switching to a playlist that might save us a fraction of a second.
  33492. if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
  33493. minimumTimeSaving = 1;
  33494. }
  33495. if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
  33496. return false;
  33497. } // set the bandwidth to that of the desired playlist being sure to scale by
  33498. // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
  33499. // don't trigger a bandwidthupdate as the bandwidth is artifial
  33500. this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
  33501. this.abort();
  33502. this.trigger('earlyabort');
  33503. return true;
  33504. }
  33505. /**
  33506. * XHR `progress` event handler
  33507. *
  33508. * @param {Event}
  33509. * The XHR `progress` event
  33510. * @param {Object} simpleSegment
  33511. * A simplified segment object copy
  33512. * @private
  33513. */
  33514. }, {
  33515. key: 'handleProgress_',
  33516. value: function handleProgress_(event, simpleSegment) {
  33517. if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
  33518. return;
  33519. }
  33520. this.trigger('progress');
  33521. }
  33522. /**
  33523. * load a specific segment from a request into the buffer
  33524. *
  33525. * @private
  33526. */
  33527. }, {
  33528. key: 'loadSegment_',
  33529. value: function loadSegment_(segmentInfo) {
  33530. this.state = 'WAITING';
  33531. this.pendingSegment_ = segmentInfo;
  33532. this.trimBackBuffer_(segmentInfo);
  33533. segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback
  33534. this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
  33535. }
  33536. /**
  33537. * trim the back buffer so that we don't have too much data
  33538. * in the source buffer
  33539. *
  33540. * @private
  33541. *
  33542. * @param {Object} segmentInfo - the current segment
  33543. */
  33544. }, {
  33545. key: 'trimBackBuffer_',
  33546. value: function trimBackBuffer_(segmentInfo) {
  33547. var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
  33548. // buffer and a very conservative "garbage collector"
  33549. // We manually clear out the old buffer to ensure
  33550. // we don't trigger the QuotaExceeded error
  33551. // on the source buffer during subsequent appends
  33552. if (removeToTime > 0) {
  33553. this.remove(0, removeToTime);
  33554. }
  33555. }
  33556. /**
  33557. * created a simplified copy of the segment object with just the
  33558. * information necessary to perform the XHR and decryption
  33559. *
  33560. * @private
  33561. *
  33562. * @param {Object} segmentInfo - the current segment
  33563. * @returns {Object} a simplified segment object copy
  33564. */
  33565. }, {
  33566. key: 'createSimplifiedSegmentObj_',
  33567. value: function createSimplifiedSegmentObj_(segmentInfo) {
  33568. var segment = segmentInfo.segment;
  33569. var simpleSegment = {
  33570. resolvedUri: segment.resolvedUri,
  33571. byterange: segment.byterange,
  33572. requestId: segmentInfo.requestId
  33573. };
  33574. if (segment.key) {
  33575. // if the media sequence is greater than 2^32, the IV will be incorrect
  33576. // assuming 10s segments, that would be about 1300 years
  33577. var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
  33578. simpleSegment.key = this.segmentKey(segment.key);
  33579. simpleSegment.key.iv = iv;
  33580. }
  33581. if (segment.map) {
  33582. simpleSegment.map = this.initSegment(segment.map);
  33583. }
  33584. return simpleSegment;
  33585. }
  33586. /**
  33587. * Handle the callback from the segmentRequest function and set the
  33588. * associated SegmentLoader state and errors if necessary
  33589. *
  33590. * @private
  33591. */
  33592. }, {
  33593. key: 'segmentRequestFinished_',
  33594. value: function segmentRequestFinished_(error, simpleSegment) {
  33595. // every request counts as a media request even if it has been aborted
  33596. // or canceled due to a timeout
  33597. this.mediaRequests += 1;
  33598. if (simpleSegment.stats) {
  33599. this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
  33600. this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
  33601. } // The request was aborted and the SegmentLoader has already been reset
  33602. if (!this.pendingSegment_) {
  33603. this.mediaRequestsAborted += 1;
  33604. return;
  33605. } // the request was aborted and the SegmentLoader has already started
  33606. // another request. this can happen when the timeout for an aborted
  33607. // request triggers due to a limitation in the XHR library
  33608. // do not count this as any sort of request or we risk double-counting
  33609. if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
  33610. return;
  33611. } // an error occurred from the active pendingSegment_ so reset everything
  33612. if (error) {
  33613. this.pendingSegment_ = null;
  33614. this.state = 'READY'; // the requests were aborted just record the aborted stat and exit
  33615. // this is not a true error condition and nothing corrective needs
  33616. // to be done
  33617. if (error.code === REQUEST_ERRORS.ABORTED) {
  33618. this.mediaRequestsAborted += 1;
  33619. return;
  33620. }
  33621. this.pause(); // the error is really just that at least one of the requests timed-out
  33622. // set the bandwidth to a very low value and trigger an ABR switch to
  33623. // take emergency action
  33624. if (error.code === REQUEST_ERRORS.TIMEOUT) {
  33625. this.mediaRequestsTimedout += 1;
  33626. this.bandwidth = 1;
  33627. this.roundTrip = NaN;
  33628. this.trigger('bandwidthupdate');
  33629. return;
  33630. } // if control-flow has arrived here, then the error is real
  33631. // emit an error event to blacklist the current playlist
  33632. this.mediaRequestsErrored += 1;
  33633. this.error(error);
  33634. this.trigger('error');
  33635. return;
  33636. } // the response was a success so set any bandwidth stats the request
  33637. // generated for ABR purposes
  33638. this.bandwidth = simpleSegment.stats.bandwidth;
  33639. this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data
  33640. // to the initSegment cache
  33641. if (simpleSegment.map) {
  33642. simpleSegment.map = this.initSegment(simpleSegment.map, true);
  33643. } // if this request included a segment key, save that data in the cache
  33644. if (simpleSegment.key) {
  33645. this.segmentKey(simpleSegment.key, true);
  33646. }
  33647. this.processSegmentResponse_(simpleSegment);
  33648. }
  33649. /**
  33650. * Move any important data from the simplified segment object
  33651. * back to the real segment object for future phases
  33652. *
  33653. * @private
  33654. */
  33655. }, {
  33656. key: 'processSegmentResponse_',
  33657. value: function processSegmentResponse_(simpleSegment) {
  33658. var segmentInfo = this.pendingSegment_;
  33659. segmentInfo.bytes = simpleSegment.bytes;
  33660. if (simpleSegment.map) {
  33661. segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
  33662. }
  33663. segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks
  33664. if (simpleSegment.fmp4Captions) {
  33665. createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams);
  33666. addCaptionData({
  33667. inbandTextTracks: this.inbandTextTracks_,
  33668. captionArray: simpleSegment.fmp4Captions,
  33669. // fmp4s will not have a timestamp offset
  33670. timestampOffset: 0
  33671. }); // Reset stored captions since we added parsed
  33672. // captions to a text track at this point
  33673. this.captionParser_.clearParsedCaptions();
  33674. }
  33675. this.handleSegment_();
  33676. }
  33677. /**
  33678. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  33679. *
  33680. * @private
  33681. */
  33682. }, {
  33683. key: 'handleSegment_',
  33684. value: function handleSegment_() {
  33685. var _this3 = this;
  33686. if (!this.pendingSegment_) {
  33687. this.state = 'READY';
  33688. return;
  33689. }
  33690. var segmentInfo = this.pendingSegment_;
  33691. var segment = segmentInfo.segment;
  33692. var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is
  33693. // dealing with. Although we're maintaining extra state, it helps to preserve the
  33694. // separation of segment loader from the actual source buffers.
  33695. if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are
  33696. // certain that all streams will provide it.
  33697. timingInfo.containsAudio || timingInfo.containsVideo)) {
  33698. this.startingMedia_ = {
  33699. containsAudio: timingInfo.containsAudio,
  33700. containsVideo: timingInfo.containsVideo
  33701. };
  33702. }
  33703. var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
  33704. if (illegalMediaSwitchError) {
  33705. this.error({
  33706. message: illegalMediaSwitchError,
  33707. blacklistDuration: Infinity
  33708. });
  33709. this.trigger('error');
  33710. return;
  33711. }
  33712. if (segmentInfo.isSyncRequest) {
  33713. this.trigger('syncinfoupdate');
  33714. this.pendingSegment_ = null;
  33715. this.state = 'READY';
  33716. return;
  33717. }
  33718. if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
  33719. this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities)
  33720. this.trigger('timestampoffset');
  33721. }
  33722. var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
  33723. if (timelineMapping !== null) {
  33724. this.trigger({
  33725. type: 'segmenttimemapping',
  33726. mapping: timelineMapping
  33727. });
  33728. }
  33729. this.state = 'APPENDING'; // if the media initialization segment is changing, append it
  33730. // before the content segment
  33731. if (segment.map) {
  33732. var initId = initSegmentId(segment.map);
  33733. if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
  33734. var initSegment = this.initSegment(segment.map);
  33735. this.sourceUpdater_.appendBuffer({
  33736. bytes: initSegment.bytes
  33737. }, function () {
  33738. _this3.activeInitSegmentId_ = initId;
  33739. });
  33740. }
  33741. }
  33742. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  33743. if (typeof segment.start === 'number' && typeof segment.end === 'number') {
  33744. this.mediaSecondsLoaded += segment.end - segment.start;
  33745. } else {
  33746. this.mediaSecondsLoaded += segment.duration;
  33747. }
  33748. this.logger_(segmentInfoString(segmentInfo));
  33749. this.sourceUpdater_.appendBuffer({
  33750. bytes: segmentInfo.bytes,
  33751. videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId)
  33752. }, this.handleUpdateEnd_.bind(this));
  33753. }
  33754. }, {
  33755. key: 'handleVideoSegmentTimingInfo_',
  33756. value: function handleVideoSegmentTimingInfo_(requestId, event) {
  33757. if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
  33758. return;
  33759. }
  33760. var segment = this.pendingSegment_.segment;
  33761. if (!segment.videoTimingInfo) {
  33762. segment.videoTimingInfo = {};
  33763. }
  33764. segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0;
  33765. segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation;
  33766. segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging
  33767. segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime;
  33768. }
  33769. /**
  33770. * callback to run when appendBuffer is finished. detects if we are
  33771. * in a good state to do things with the data we got, or if we need
  33772. * to wait for more
  33773. *
  33774. * @private
  33775. */
  33776. }, {
  33777. key: 'handleUpdateEnd_',
  33778. value: function handleUpdateEnd_() {
  33779. if (!this.pendingSegment_) {
  33780. this.state = 'READY';
  33781. if (!this.paused()) {
  33782. this.monitorBuffer_();
  33783. }
  33784. return;
  33785. }
  33786. var segmentInfo = this.pendingSegment_;
  33787. var segment = segmentInfo.segment;
  33788. var isWalkingForward = this.mediaIndex !== null;
  33789. this.pendingSegment_ = null;
  33790. this.recordThroughput_(segmentInfo);
  33791. this.addSegmentMetadataCue_(segmentInfo);
  33792. this.state = 'READY';
  33793. this.mediaIndex = segmentInfo.mediaIndex;
  33794. this.fetchAtBuffer_ = true;
  33795. this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
  33796. // the following conditional otherwise it may consider this a bad "guess"
  33797. // and attempt to resync when the post-update seekable window and live
  33798. // point would mean that this was the perfect segment to fetch
  33799. this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before
  33800. // the currentTime_ that means that our conservative guess was too conservative.
  33801. // In that case, reset the loader state so that we try to use any information gained
  33802. // from the previous request to create a new, more accurate, sync-point.
  33803. if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
  33804. this.resetEverything();
  33805. return;
  33806. } // Don't do a rendition switch unless we have enough time to get a sync segment
  33807. // and conservatively guess
  33808. if (isWalkingForward) {
  33809. this.trigger('bandwidthupdate');
  33810. }
  33811. this.trigger('progress'); // any time an update finishes and the last segment is in the
  33812. // buffer, end the stream. this ensures the "ended" event will
  33813. // fire if playback reaches that point.
  33814. if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) {
  33815. this.endOfStream();
  33816. }
  33817. if (!this.paused()) {
  33818. this.monitorBuffer_();
  33819. }
  33820. }
  33821. /**
  33822. * Records the current throughput of the decrypt, transmux, and append
  33823. * portion of the semgment pipeline. `throughput.rate` is a the cumulative
  33824. * moving average of the throughput. `throughput.count` is the number of
  33825. * data points in the average.
  33826. *
  33827. * @private
  33828. * @param {Object} segmentInfo the object returned by loadSegment
  33829. */
  33830. }, {
  33831. key: 'recordThroughput_',
  33832. value: function recordThroughput_(segmentInfo) {
  33833. var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
  33834. // by zero in the case where the throughput is ridiculously high
  33835. var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
  33836. var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
  33837. // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
  33838. this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
  33839. }
  33840. /**
  33841. * Adds a cue to the segment-metadata track with some metadata information about the
  33842. * segment
  33843. *
  33844. * @private
  33845. * @param {Object} segmentInfo
  33846. * the object returned by loadSegment
  33847. * @method addSegmentMetadataCue_
  33848. */
  33849. }, {
  33850. key: 'addSegmentMetadataCue_',
  33851. value: function addSegmentMetadataCue_(segmentInfo) {
  33852. if (!this.segmentMetadataTrack_) {
  33853. return;
  33854. }
  33855. var segment = segmentInfo.segment;
  33856. var start = segment.start;
  33857. var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
  33858. if (!finite(start) || !finite(end)) {
  33859. return;
  33860. }
  33861. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  33862. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  33863. var value = {
  33864. custom: segment.custom,
  33865. dateTimeObject: segment.dateTimeObject,
  33866. dateTimeString: segment.dateTimeString,
  33867. bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
  33868. resolution: segmentInfo.playlist.attributes.RESOLUTION,
  33869. codecs: segmentInfo.playlist.attributes.CODECS,
  33870. byteLength: segmentInfo.byteLength,
  33871. uri: segmentInfo.uri,
  33872. timeline: segmentInfo.timeline,
  33873. playlist: segmentInfo.playlist.uri,
  33874. start: start,
  33875. end: end
  33876. };
  33877. var data = JSON.stringify(value);
  33878. var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
  33879. // the differences of WebKitDataCue in safari and VTTCue in other browsers
  33880. cue.value = value;
  33881. this.segmentMetadataTrack_.addCue(cue);
  33882. }
  33883. }]);
  33884. return SegmentLoader;
  33885. }(videojs$1.EventTarget);
  33886. var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
  33887. return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
  33888. };
  33889. /**
  33890. * @file vtt-segment-loader.js
  33891. */
  33892. var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) {
  33893. return char.charCodeAt(0);
  33894. }));
  33895. /**
  33896. * An object that manages segment loading and appending.
  33897. *
  33898. * @class VTTSegmentLoader
  33899. * @param {Object} options required and optional options
  33900. * @extends videojs.EventTarget
  33901. */
  33902. var VTTSegmentLoader = function (_SegmentLoader) {
  33903. inherits(VTTSegmentLoader, _SegmentLoader);
  33904. function VTTSegmentLoader(settings) {
  33905. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  33906. classCallCheck(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error;
  33907. // however, VTTSegmentLoader has no need of a media source, so delete the reference
  33908. var _this = possibleConstructorReturn(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
  33909. _this.mediaSource_ = null;
  33910. _this.subtitlesTrack_ = null;
  33911. return _this;
  33912. }
  33913. /**
  33914. * Indicates which time ranges are buffered
  33915. *
  33916. * @return {TimeRange}
  33917. * TimeRange object representing the current buffered ranges
  33918. */
  33919. createClass(VTTSegmentLoader, [{
  33920. key: 'buffered_',
  33921. value: function buffered_() {
  33922. if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
  33923. return videojs$1.createTimeRanges();
  33924. }
  33925. var cues = this.subtitlesTrack_.cues;
  33926. var start = cues[0].startTime;
  33927. var end = cues[cues.length - 1].startTime;
  33928. return videojs$1.createTimeRanges([[start, end]]);
  33929. }
  33930. /**
  33931. * Gets and sets init segment for the provided map
  33932. *
  33933. * @param {Object} map
  33934. * The map object representing the init segment to get or set
  33935. * @param {Boolean=} set
  33936. * If true, the init segment for the provided map should be saved
  33937. * @return {Object}
  33938. * map object for desired init segment
  33939. */
  33940. }, {
  33941. key: 'initSegment',
  33942. value: function initSegment(map) {
  33943. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  33944. if (!map) {
  33945. return null;
  33946. }
  33947. var id = initSegmentId(map);
  33948. var storedMap = this.initSegments_[id];
  33949. if (set$$1 && !storedMap && map.bytes) {
  33950. // append WebVTT line terminators to the media initialization segment if it exists
  33951. // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
  33952. // requires two or more WebVTT line terminators between the WebVTT header and the
  33953. // rest of the file
  33954. var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
  33955. var combinedSegment = new Uint8Array(combinedByteLength);
  33956. combinedSegment.set(map.bytes);
  33957. combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
  33958. this.initSegments_[id] = storedMap = {
  33959. resolvedUri: map.resolvedUri,
  33960. byterange: map.byterange,
  33961. bytes: combinedSegment
  33962. };
  33963. }
  33964. return storedMap || map;
  33965. }
  33966. /**
  33967. * Returns true if all configuration required for loading is present, otherwise false.
  33968. *
  33969. * @return {Boolean} True if the all configuration is ready for loading
  33970. * @private
  33971. */
  33972. }, {
  33973. key: 'couldBeginLoading_',
  33974. value: function couldBeginLoading_() {
  33975. return this.playlist_ && this.subtitlesTrack_ && !this.paused();
  33976. }
  33977. /**
  33978. * Once all the starting parameters have been specified, begin
  33979. * operation. This method should only be invoked from the INIT
  33980. * state.
  33981. *
  33982. * @private
  33983. */
  33984. }, {
  33985. key: 'init_',
  33986. value: function init_() {
  33987. this.state = 'READY';
  33988. this.resetEverything();
  33989. return this.monitorBuffer_();
  33990. }
  33991. /**
  33992. * Set a subtitle track on the segment loader to add subtitles to
  33993. *
  33994. * @param {TextTrack=} track
  33995. * The text track to add loaded subtitles to
  33996. * @return {TextTrack}
  33997. * Returns the subtitles track
  33998. */
  33999. }, {
  34000. key: 'track',
  34001. value: function track(_track) {
  34002. if (typeof _track === 'undefined') {
  34003. return this.subtitlesTrack_;
  34004. }
  34005. this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
  34006. // buffering now
  34007. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  34008. this.init_();
  34009. }
  34010. return this.subtitlesTrack_;
  34011. }
  34012. /**
  34013. * Remove any data in the source buffer between start and end times
  34014. * @param {Number} start - the start time of the region to remove from the buffer
  34015. * @param {Number} end - the end time of the region to remove from the buffer
  34016. */
  34017. }, {
  34018. key: 'remove',
  34019. value: function remove(start, end) {
  34020. removeCuesFromTrack(start, end, this.subtitlesTrack_);
  34021. }
  34022. /**
  34023. * fill the buffer with segements unless the sourceBuffers are
  34024. * currently updating
  34025. *
  34026. * Note: this function should only ever be called by monitorBuffer_
  34027. * and never directly
  34028. *
  34029. * @private
  34030. */
  34031. }, {
  34032. key: 'fillBuffer_',
  34033. value: function fillBuffer_() {
  34034. var _this2 = this;
  34035. if (!this.syncPoint_) {
  34036. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  34037. } // see if we need to begin loading immediately
  34038. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  34039. segmentInfo = this.skipEmptySegments_(segmentInfo);
  34040. if (!segmentInfo) {
  34041. return;
  34042. }
  34043. if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
  34044. // We don't have the timestamp offset that we need to sync subtitles.
  34045. // Rerun on a timestamp offset or user interaction.
  34046. var checkTimestampOffset = function checkTimestampOffset() {
  34047. _this2.state = 'READY';
  34048. if (!_this2.paused()) {
  34049. // if not paused, queue a buffer check as soon as possible
  34050. _this2.monitorBuffer_();
  34051. }
  34052. };
  34053. this.syncController_.one('timestampoffset', checkTimestampOffset);
  34054. this.state = 'WAITING_ON_TIMELINE';
  34055. return;
  34056. }
  34057. this.loadSegment_(segmentInfo);
  34058. }
  34059. /**
  34060. * Prevents the segment loader from requesting segments we know contain no subtitles
  34061. * by walking forward until we find the next segment that we don't know whether it is
  34062. * empty or not.
  34063. *
  34064. * @param {Object} segmentInfo
  34065. * a segment info object that describes the current segment
  34066. * @return {Object}
  34067. * a segment info object that describes the current segment
  34068. */
  34069. }, {
  34070. key: 'skipEmptySegments_',
  34071. value: function skipEmptySegments_(segmentInfo) {
  34072. while (segmentInfo && segmentInfo.segment.empty) {
  34073. segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
  34074. }
  34075. return segmentInfo;
  34076. }
  34077. /**
  34078. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  34079. *
  34080. * @private
  34081. */
  34082. }, {
  34083. key: 'handleSegment_',
  34084. value: function handleSegment_() {
  34085. var _this3 = this;
  34086. if (!this.pendingSegment_ || !this.subtitlesTrack_) {
  34087. this.state = 'READY';
  34088. return;
  34089. }
  34090. this.state = 'APPENDING';
  34091. var segmentInfo = this.pendingSegment_;
  34092. var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  34093. if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
  34094. var loadHandler = function loadHandler() {
  34095. _this3.handleSegment_();
  34096. };
  34097. this.state = 'WAITING_ON_VTTJS';
  34098. this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
  34099. this.subtitlesTrack_.tech_.one('vttjserror', function () {
  34100. _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
  34101. _this3.error({
  34102. message: 'Error loading vtt.js'
  34103. });
  34104. _this3.state = 'READY';
  34105. _this3.pause();
  34106. _this3.trigger('error');
  34107. });
  34108. return;
  34109. }
  34110. segment.requested = true;
  34111. try {
  34112. this.parseVTTCues_(segmentInfo);
  34113. } catch (e) {
  34114. this.error({
  34115. message: e.message
  34116. });
  34117. this.state = 'READY';
  34118. this.pause();
  34119. return this.trigger('error');
  34120. }
  34121. this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
  34122. if (segmentInfo.isSyncRequest) {
  34123. this.trigger('syncinfoupdate');
  34124. this.pendingSegment_ = null;
  34125. this.state = 'READY';
  34126. return;
  34127. }
  34128. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  34129. this.mediaSecondsLoaded += segment.duration;
  34130. if (segmentInfo.cues.length) {
  34131. // remove any overlapping cues to prevent doubling
  34132. this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
  34133. }
  34134. segmentInfo.cues.forEach(function (cue) {
  34135. _this3.subtitlesTrack_.addCue(cue);
  34136. });
  34137. this.handleUpdateEnd_();
  34138. }
  34139. /**
  34140. * Uses the WebVTT parser to parse the segment response
  34141. *
  34142. * @param {Object} segmentInfo
  34143. * a segment info object that describes the current segment
  34144. * @private
  34145. */
  34146. }, {
  34147. key: 'parseVTTCues_',
  34148. value: function parseVTTCues_(segmentInfo) {
  34149. var decoder = void 0;
  34150. var decodeBytesToString = false;
  34151. if (typeof window$1.TextDecoder === 'function') {
  34152. decoder = new window$1.TextDecoder('utf8');
  34153. } else {
  34154. decoder = window$1.WebVTT.StringDecoder();
  34155. decodeBytesToString = true;
  34156. }
  34157. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
  34158. segmentInfo.cues = [];
  34159. segmentInfo.timestampmap = {
  34160. MPEGTS: 0,
  34161. LOCAL: 0
  34162. };
  34163. parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
  34164. parser.ontimestampmap = function (map) {
  34165. return segmentInfo.timestampmap = map;
  34166. };
  34167. parser.onparsingerror = function (error) {
  34168. videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
  34169. };
  34170. if (segmentInfo.segment.map) {
  34171. var mapData = segmentInfo.segment.map.bytes;
  34172. if (decodeBytesToString) {
  34173. mapData = uint8ToUtf8(mapData);
  34174. }
  34175. parser.parse(mapData);
  34176. }
  34177. var segmentData = segmentInfo.bytes;
  34178. if (decodeBytesToString) {
  34179. segmentData = uint8ToUtf8(segmentData);
  34180. }
  34181. parser.parse(segmentData);
  34182. parser.flush();
  34183. }
  34184. /**
  34185. * Updates the start and end times of any cues parsed by the WebVTT parser using
  34186. * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
  34187. * from the SyncController
  34188. *
  34189. * @param {Object} segmentInfo
  34190. * a segment info object that describes the current segment
  34191. * @param {Object} mappingObj
  34192. * object containing a mapping from TS to media time
  34193. * @param {Object} playlist
  34194. * the playlist object containing the segment
  34195. * @private
  34196. */
  34197. }, {
  34198. key: 'updateTimeMapping_',
  34199. value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
  34200. var segment = segmentInfo.segment;
  34201. if (!mappingObj) {
  34202. // If the sync controller does not have a mapping of TS to Media Time for the
  34203. // timeline, then we don't have enough information to update the cue
  34204. // start/end times
  34205. return;
  34206. }
  34207. if (!segmentInfo.cues.length) {
  34208. // If there are no cues, we also do not have enough information to figure out
  34209. // segment timing. Mark that the segment contains no cues so we don't re-request
  34210. // an empty segment.
  34211. segment.empty = true;
  34212. return;
  34213. }
  34214. var timestampmap = segmentInfo.timestampmap;
  34215. var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
  34216. segmentInfo.cues.forEach(function (cue) {
  34217. // First convert cue time to TS time using the timestamp-map provided within the vtt
  34218. cue.startTime += diff;
  34219. cue.endTime += diff;
  34220. });
  34221. if (!playlist.syncInfo) {
  34222. var firstStart = segmentInfo.cues[0].startTime;
  34223. var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
  34224. playlist.syncInfo = {
  34225. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  34226. time: Math.min(firstStart, lastStart - segment.duration)
  34227. };
  34228. }
  34229. }
  34230. }]);
  34231. return VTTSegmentLoader;
  34232. }(SegmentLoader);
  34233. /**
  34234. * @file ad-cue-tags.js
  34235. */
  34236. /**
  34237. * Searches for an ad cue that overlaps with the given mediaTime
  34238. */
  34239. var findAdCue = function findAdCue(track, mediaTime) {
  34240. var cues = track.cues;
  34241. for (var i = 0; i < cues.length; i++) {
  34242. var cue = cues[i];
  34243. if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
  34244. return cue;
  34245. }
  34246. }
  34247. return null;
  34248. };
  34249. var updateAdCues = function updateAdCues(media, track) {
  34250. var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  34251. if (!media.segments) {
  34252. return;
  34253. }
  34254. var mediaTime = offset;
  34255. var cue = void 0;
  34256. for (var i = 0; i < media.segments.length; i++) {
  34257. var segment = media.segments[i];
  34258. if (!cue) {
  34259. // Since the cues will span for at least the segment duration, adding a fudge
  34260. // factor of half segment duration will prevent duplicate cues from being
  34261. // created when timing info is not exact (e.g. cue start time initialized
  34262. // at 10.006677, but next call mediaTime is 10.003332 )
  34263. cue = findAdCue(track, mediaTime + segment.duration / 2);
  34264. }
  34265. if (cue) {
  34266. if ('cueIn' in segment) {
  34267. // Found a CUE-IN so end the cue
  34268. cue.endTime = mediaTime;
  34269. cue.adEndTime = mediaTime;
  34270. mediaTime += segment.duration;
  34271. cue = null;
  34272. continue;
  34273. }
  34274. if (mediaTime < cue.endTime) {
  34275. // Already processed this mediaTime for this cue
  34276. mediaTime += segment.duration;
  34277. continue;
  34278. } // otherwise extend cue until a CUE-IN is found
  34279. cue.endTime += segment.duration;
  34280. } else {
  34281. if ('cueOut' in segment) {
  34282. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
  34283. cue.adStartTime = mediaTime; // Assumes tag format to be
  34284. // #EXT-X-CUE-OUT:30
  34285. cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
  34286. track.addCue(cue);
  34287. }
  34288. if ('cueOutCont' in segment) {
  34289. // Entered into the middle of an ad cue
  34290. var adOffset = void 0;
  34291. var adTotal = void 0; // Assumes tag formate to be
  34292. // #EXT-X-CUE-OUT-CONT:10/30
  34293. var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
  34294. var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2);
  34295. adOffset = _segment$cueOutCont$s2[0];
  34296. adTotal = _segment$cueOutCont$s2[1];
  34297. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
  34298. cue.adStartTime = mediaTime - adOffset;
  34299. cue.adEndTime = cue.adStartTime + adTotal;
  34300. track.addCue(cue);
  34301. }
  34302. }
  34303. mediaTime += segment.duration;
  34304. }
  34305. };
  34306. /**
  34307. * @file sync-controller.js
  34308. */
  34309. var tsprobe = tsInspector.inspect;
  34310. var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
  34311. // the equivalence display-time 0 === segment-index 0
  34312. {
  34313. name: 'VOD',
  34314. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34315. if (duration$$1 !== Infinity) {
  34316. var syncPoint = {
  34317. time: 0,
  34318. segmentIndex: 0
  34319. };
  34320. return syncPoint;
  34321. }
  34322. return null;
  34323. }
  34324. }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
  34325. {
  34326. name: 'ProgramDateTime',
  34327. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34328. if (!syncController.datetimeToDisplayTime) {
  34329. return null;
  34330. }
  34331. var segments = playlist.segments || [];
  34332. var syncPoint = null;
  34333. var lastDistance = null;
  34334. currentTime = currentTime || 0;
  34335. for (var i = 0; i < segments.length; i++) {
  34336. var segment = segments[i];
  34337. if (segment.dateTimeObject) {
  34338. var segmentTime = segment.dateTimeObject.getTime() / 1000;
  34339. var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
  34340. var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, we have passed
  34341. // currentTime and can stop looking for better candidates
  34342. if (lastDistance !== null && lastDistance < distance) {
  34343. break;
  34344. }
  34345. lastDistance = distance;
  34346. syncPoint = {
  34347. time: segmentStart,
  34348. segmentIndex: i
  34349. };
  34350. }
  34351. }
  34352. return syncPoint;
  34353. }
  34354. }, // Stategy "Segment": We have a known time mapping for a timeline and a
  34355. // segment in the current timeline with timing data
  34356. {
  34357. name: 'Segment',
  34358. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34359. var segments = playlist.segments || [];
  34360. var syncPoint = null;
  34361. var lastDistance = null;
  34362. currentTime = currentTime || 0;
  34363. for (var i = 0; i < segments.length; i++) {
  34364. var segment = segments[i];
  34365. if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
  34366. var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed
  34367. // currentTime and can stop looking for better candidates
  34368. if (lastDistance !== null && lastDistance < distance) {
  34369. break;
  34370. }
  34371. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  34372. lastDistance = distance;
  34373. syncPoint = {
  34374. time: segment.start,
  34375. segmentIndex: i
  34376. };
  34377. }
  34378. }
  34379. }
  34380. return syncPoint;
  34381. }
  34382. }, // Stategy "Discontinuity": We have a discontinuity with a known
  34383. // display-time
  34384. {
  34385. name: 'Discontinuity',
  34386. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34387. var syncPoint = null;
  34388. currentTime = currentTime || 0;
  34389. if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  34390. var lastDistance = null;
  34391. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  34392. var segmentIndex = playlist.discontinuityStarts[i];
  34393. var discontinuity = playlist.discontinuitySequence + i + 1;
  34394. var discontinuitySync = syncController.discontinuities[discontinuity];
  34395. if (discontinuitySync) {
  34396. var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
  34397. // currentTime and can stop looking for better candidates
  34398. if (lastDistance !== null && lastDistance < distance) {
  34399. break;
  34400. }
  34401. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  34402. lastDistance = distance;
  34403. syncPoint = {
  34404. time: discontinuitySync.time,
  34405. segmentIndex: segmentIndex
  34406. };
  34407. }
  34408. }
  34409. }
  34410. }
  34411. return syncPoint;
  34412. }
  34413. }, // Stategy "Playlist": We have a playlist with a known mapping of
  34414. // segment index to display time
  34415. {
  34416. name: 'Playlist',
  34417. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34418. if (playlist.syncInfo) {
  34419. var syncPoint = {
  34420. time: playlist.syncInfo.time,
  34421. segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
  34422. };
  34423. return syncPoint;
  34424. }
  34425. return null;
  34426. }
  34427. }];
  34428. var SyncController = function (_videojs$EventTarget) {
  34429. inherits(SyncController, _videojs$EventTarget);
  34430. function SyncController() {
  34431. classCallCheck(this, SyncController); // Segment Loader state variables...
  34432. // ...for synching across variants
  34433. var _this = possibleConstructorReturn(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
  34434. _this.inspectCache_ = undefined; // ...for synching across variants
  34435. _this.timelines = [];
  34436. _this.discontinuities = [];
  34437. _this.datetimeToDisplayTime = null;
  34438. _this.logger_ = logger('SyncController');
  34439. return _this;
  34440. }
  34441. /**
  34442. * Find a sync-point for the playlist specified
  34443. *
  34444. * A sync-point is defined as a known mapping from display-time to
  34445. * a segment-index in the current playlist.
  34446. *
  34447. * @param {Playlist} playlist
  34448. * The playlist that needs a sync-point
  34449. * @param {Number} duration
  34450. * Duration of the MediaSource (Infinite if playing a live source)
  34451. * @param {Number} currentTimeline
  34452. * The last timeline from which a segment was loaded
  34453. * @returns {Object}
  34454. * A sync-point object
  34455. */
  34456. createClass(SyncController, [{
  34457. key: 'getSyncPoint',
  34458. value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
  34459. var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
  34460. if (!syncPoints.length) {
  34461. // Signal that we need to attempt to get a sync-point manually
  34462. // by fetching a segment in the playlist and constructing
  34463. // a sync-point from that information
  34464. return null;
  34465. } // Now find the sync-point that is closest to the currentTime because
  34466. // that should result in the most accurate guess about which segment
  34467. // to fetch
  34468. return this.selectSyncPoint_(syncPoints, {
  34469. key: 'time',
  34470. value: currentTime
  34471. });
  34472. }
  34473. /**
  34474. * Calculate the amount of time that has expired off the playlist during playback
  34475. *
  34476. * @param {Playlist} playlist
  34477. * Playlist object to calculate expired from
  34478. * @param {Number} duration
  34479. * Duration of the MediaSource (Infinity if playling a live source)
  34480. * @returns {Number|null}
  34481. * The amount of time that has expired off the playlist during playback. Null
  34482. * if no sync-points for the playlist can be found.
  34483. */
  34484. }, {
  34485. key: 'getExpiredTime',
  34486. value: function getExpiredTime(playlist, duration$$1) {
  34487. if (!playlist || !playlist.segments) {
  34488. return null;
  34489. }
  34490. var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
  34491. if (!syncPoints.length) {
  34492. return null;
  34493. }
  34494. var syncPoint = this.selectSyncPoint_(syncPoints, {
  34495. key: 'segmentIndex',
  34496. value: 0
  34497. }); // If the sync-point is beyond the start of the playlist, we want to subtract the
  34498. // duration from index 0 to syncPoint.segmentIndex instead of adding.
  34499. if (syncPoint.segmentIndex > 0) {
  34500. syncPoint.time *= -1;
  34501. }
  34502. return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
  34503. }
  34504. /**
  34505. * Runs each sync-point strategy and returns a list of sync-points returned by the
  34506. * strategies
  34507. *
  34508. * @private
  34509. * @param {Playlist} playlist
  34510. * The playlist that needs a sync-point
  34511. * @param {Number} duration
  34512. * Duration of the MediaSource (Infinity if playing a live source)
  34513. * @param {Number} currentTimeline
  34514. * The last timeline from which a segment was loaded
  34515. * @returns {Array}
  34516. * A list of sync-point objects
  34517. */
  34518. }, {
  34519. key: 'runStrategies_',
  34520. value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
  34521. var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
  34522. for (var i = 0; i < syncPointStrategies.length; i++) {
  34523. var strategy = syncPointStrategies[i];
  34524. var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
  34525. if (syncPoint) {
  34526. syncPoint.strategy = strategy.name;
  34527. syncPoints.push({
  34528. strategy: strategy.name,
  34529. syncPoint: syncPoint
  34530. });
  34531. }
  34532. }
  34533. return syncPoints;
  34534. }
  34535. /**
  34536. * Selects the sync-point nearest the specified target
  34537. *
  34538. * @private
  34539. * @param {Array} syncPoints
  34540. * List of sync-points to select from
  34541. * @param {Object} target
  34542. * Object specifying the property and value we are targeting
  34543. * @param {String} target.key
  34544. * Specifies the property to target. Must be either 'time' or 'segmentIndex'
  34545. * @param {Number} target.value
  34546. * The value to target for the specified key.
  34547. * @returns {Object}
  34548. * The sync-point nearest the target
  34549. */
  34550. }, {
  34551. key: 'selectSyncPoint_',
  34552. value: function selectSyncPoint_(syncPoints, target) {
  34553. var bestSyncPoint = syncPoints[0].syncPoint;
  34554. var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
  34555. var bestStrategy = syncPoints[0].strategy;
  34556. for (var i = 1; i < syncPoints.length; i++) {
  34557. var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
  34558. if (newDistance < bestDistance) {
  34559. bestDistance = newDistance;
  34560. bestSyncPoint = syncPoints[i].syncPoint;
  34561. bestStrategy = syncPoints[i].strategy;
  34562. }
  34563. }
  34564. this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
  34565. return bestSyncPoint;
  34566. }
  34567. /**
  34568. * Save any meta-data present on the segments when segments leave
  34569. * the live window to the playlist to allow for synchronization at the
  34570. * playlist level later.
  34571. *
  34572. * @param {Playlist} oldPlaylist - The previous active playlist
  34573. * @param {Playlist} newPlaylist - The updated and most current playlist
  34574. */
  34575. }, {
  34576. key: 'saveExpiredSegmentInfo',
  34577. value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
  34578. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
  34579. // save that information as a possible sync-point reference in future
  34580. for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
  34581. var lastRemovedSegment = oldPlaylist.segments[i];
  34582. if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
  34583. newPlaylist.syncInfo = {
  34584. mediaSequence: oldPlaylist.mediaSequence + i,
  34585. time: lastRemovedSegment.start
  34586. };
  34587. this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
  34588. this.trigger('syncinfoupdate');
  34589. break;
  34590. }
  34591. }
  34592. }
  34593. /**
  34594. * Save the mapping from playlist's ProgramDateTime to display. This should
  34595. * only ever happen once at the start of playback.
  34596. *
  34597. * @param {Playlist} playlist - The currently active playlist
  34598. */
  34599. }, {
  34600. key: 'setDateTimeMapping',
  34601. value: function setDateTimeMapping(playlist) {
  34602. if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
  34603. var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
  34604. this.datetimeToDisplayTime = -playlistTimestamp;
  34605. }
  34606. }
  34607. /**
  34608. * Reset the state of the inspection cache when we do a rendition
  34609. * switch
  34610. */
  34611. }, {
  34612. key: 'reset',
  34613. value: function reset() {
  34614. this.inspectCache_ = undefined;
  34615. }
  34616. /**
  34617. * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
  34618. * and end of the segment in it's internal "media time". Used to generate
  34619. * mappings from that internal "media time" to the display time that is
  34620. * shown on the player.
  34621. *
  34622. * @param {SegmentInfo} segmentInfo - The current active request information
  34623. */
  34624. }, {
  34625. key: 'probeSegmentInfo',
  34626. value: function probeSegmentInfo(segmentInfo) {
  34627. var segment = segmentInfo.segment;
  34628. var playlist = segmentInfo.playlist;
  34629. var timingInfo = void 0;
  34630. if (segment.map) {
  34631. timingInfo = this.probeMp4Segment_(segmentInfo);
  34632. } else {
  34633. timingInfo = this.probeTsSegment_(segmentInfo);
  34634. }
  34635. if (timingInfo) {
  34636. if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
  34637. this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
  34638. // now with segment timing information
  34639. if (!playlist.syncInfo) {
  34640. playlist.syncInfo = {
  34641. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  34642. time: segment.start
  34643. };
  34644. }
  34645. }
  34646. }
  34647. return timingInfo;
  34648. }
  34649. /**
  34650. * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
  34651. * in it's internal "media time".
  34652. *
  34653. * @private
  34654. * @param {SegmentInfo} segmentInfo - The current active request information
  34655. * @return {object} The start and end time of the current segment in "media time"
  34656. */
  34657. }, {
  34658. key: 'probeMp4Segment_',
  34659. value: function probeMp4Segment_(segmentInfo) {
  34660. var segment = segmentInfo.segment;
  34661. var timescales = mp4probe.timescale(segment.map.bytes);
  34662. var startTime = mp4probe.startTime(timescales, segmentInfo.bytes);
  34663. if (segmentInfo.timestampOffset !== null) {
  34664. segmentInfo.timestampOffset -= startTime;
  34665. }
  34666. return {
  34667. start: startTime,
  34668. end: startTime + segment.duration
  34669. };
  34670. }
  34671. /**
  34672. * Probe an mpeg2-ts segment to determine the start and end of the segment
  34673. * in it's internal "media time".
  34674. *
  34675. * @private
  34676. * @param {SegmentInfo} segmentInfo - The current active request information
  34677. * @return {object} The start and end time of the current segment in "media time"
  34678. */
  34679. }, {
  34680. key: 'probeTsSegment_',
  34681. value: function probeTsSegment_(segmentInfo) {
  34682. var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
  34683. var segmentStartTime = void 0;
  34684. var segmentEndTime = void 0;
  34685. if (!timeInfo) {
  34686. return null;
  34687. }
  34688. if (timeInfo.video && timeInfo.video.length === 2) {
  34689. this.inspectCache_ = timeInfo.video[1].dts;
  34690. segmentStartTime = timeInfo.video[0].dtsTime;
  34691. segmentEndTime = timeInfo.video[1].dtsTime;
  34692. } else if (timeInfo.audio && timeInfo.audio.length === 2) {
  34693. this.inspectCache_ = timeInfo.audio[1].dts;
  34694. segmentStartTime = timeInfo.audio[0].dtsTime;
  34695. segmentEndTime = timeInfo.audio[1].dtsTime;
  34696. }
  34697. var probedInfo = {
  34698. start: segmentStartTime,
  34699. end: segmentEndTime,
  34700. containsVideo: timeInfo.video && timeInfo.video.length === 2,
  34701. containsAudio: timeInfo.audio && timeInfo.audio.length === 2
  34702. };
  34703. return probedInfo;
  34704. }
  34705. }, {
  34706. key: 'timestampOffsetForTimeline',
  34707. value: function timestampOffsetForTimeline(timeline) {
  34708. if (typeof this.timelines[timeline] === 'undefined') {
  34709. return null;
  34710. }
  34711. return this.timelines[timeline].time;
  34712. }
  34713. }, {
  34714. key: 'mappingForTimeline',
  34715. value: function mappingForTimeline(timeline) {
  34716. if (typeof this.timelines[timeline] === 'undefined') {
  34717. return null;
  34718. }
  34719. return this.timelines[timeline].mapping;
  34720. }
  34721. /**
  34722. * Use the "media time" for a segment to generate a mapping to "display time" and
  34723. * save that display time to the segment.
  34724. *
  34725. * @private
  34726. * @param {SegmentInfo} segmentInfo
  34727. * The current active request information
  34728. * @param {object} timingInfo
  34729. * The start and end time of the current segment in "media time"
  34730. * @returns {Boolean}
  34731. * Returns false if segment time mapping could not be calculated
  34732. */
  34733. }, {
  34734. key: 'calculateSegmentTimeMapping_',
  34735. value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
  34736. var segment = segmentInfo.segment;
  34737. var mappingObj = this.timelines[segmentInfo.timeline];
  34738. if (segmentInfo.timestampOffset !== null) {
  34739. mappingObj = {
  34740. time: segmentInfo.startOfSegment,
  34741. mapping: segmentInfo.startOfSegment - timingInfo.start
  34742. };
  34743. this.timelines[segmentInfo.timeline] = mappingObj;
  34744. this.trigger('timestampoffset');
  34745. this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
  34746. segment.start = segmentInfo.startOfSegment;
  34747. segment.end = timingInfo.end + mappingObj.mapping;
  34748. } else if (mappingObj) {
  34749. segment.start = timingInfo.start + mappingObj.mapping;
  34750. segment.end = timingInfo.end + mappingObj.mapping;
  34751. } else {
  34752. return false;
  34753. }
  34754. return true;
  34755. }
  34756. /**
  34757. * Each time we have discontinuity in the playlist, attempt to calculate the location
  34758. * in display of the start of the discontinuity and save that. We also save an accuracy
  34759. * value so that we save values with the most accuracy (closest to 0.)
  34760. *
  34761. * @private
  34762. * @param {SegmentInfo} segmentInfo - The current active request information
  34763. */
  34764. }, {
  34765. key: 'saveDiscontinuitySyncInfo_',
  34766. value: function saveDiscontinuitySyncInfo_(segmentInfo) {
  34767. var playlist = segmentInfo.playlist;
  34768. var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
  34769. // the start of the range and it's accuracy is 0 (greater accuracy values
  34770. // mean more approximation)
  34771. if (segment.discontinuity) {
  34772. this.discontinuities[segment.timeline] = {
  34773. time: segment.start,
  34774. accuracy: 0
  34775. };
  34776. } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  34777. // Search for future discontinuities that we can provide better timing
  34778. // information for and save that information for sync purposes
  34779. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  34780. var segmentIndex = playlist.discontinuityStarts[i];
  34781. var discontinuity = playlist.discontinuitySequence + i + 1;
  34782. var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
  34783. var accuracy = Math.abs(mediaIndexDiff);
  34784. if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
  34785. var time = void 0;
  34786. if (mediaIndexDiff < 0) {
  34787. time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
  34788. } else {
  34789. time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
  34790. }
  34791. this.discontinuities[discontinuity] = {
  34792. time: time,
  34793. accuracy: accuracy
  34794. };
  34795. }
  34796. }
  34797. }
  34798. }
  34799. }]);
  34800. return SyncController;
  34801. }(videojs$1.EventTarget);
  34802. var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
  34803. var self = this;
  34804. var decrypterWorker = function () {
  34805. /*
  34806. * pkcs7.pad
  34807. * https://github.com/brightcove/pkcs7
  34808. *
  34809. * Copyright (c) 2014 Brightcove
  34810. * Licensed under the apache2 license.
  34811. */
  34812. /**
  34813. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  34814. * @param padded {Uint8Array} unencrypted bytes that have been padded
  34815. * @return {Uint8Array} the unpadded bytes
  34816. * @see http://tools.ietf.org/html/rfc5652
  34817. */
  34818. function unpad(padded) {
  34819. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  34820. }
  34821. var classCallCheck = function classCallCheck(instance, Constructor) {
  34822. if (!(instance instanceof Constructor)) {
  34823. throw new TypeError("Cannot call a class as a function");
  34824. }
  34825. };
  34826. var createClass = function () {
  34827. function defineProperties(target, props) {
  34828. for (var i = 0; i < props.length; i++) {
  34829. var descriptor = props[i];
  34830. descriptor.enumerable = descriptor.enumerable || false;
  34831. descriptor.configurable = true;
  34832. if ("value" in descriptor) descriptor.writable = true;
  34833. Object.defineProperty(target, descriptor.key, descriptor);
  34834. }
  34835. }
  34836. return function (Constructor, protoProps, staticProps) {
  34837. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  34838. if (staticProps) defineProperties(Constructor, staticProps);
  34839. return Constructor;
  34840. };
  34841. }();
  34842. var inherits = function inherits(subClass, superClass) {
  34843. if (typeof superClass !== "function" && superClass !== null) {
  34844. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  34845. }
  34846. subClass.prototype = Object.create(superClass && superClass.prototype, {
  34847. constructor: {
  34848. value: subClass,
  34849. enumerable: false,
  34850. writable: true,
  34851. configurable: true
  34852. }
  34853. });
  34854. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  34855. };
  34856. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  34857. if (!self) {
  34858. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  34859. }
  34860. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  34861. };
  34862. /**
  34863. * @file aes.js
  34864. *
  34865. * This file contains an adaptation of the AES decryption algorithm
  34866. * from the Standford Javascript Cryptography Library. That work is
  34867. * covered by the following copyright and permissions notice:
  34868. *
  34869. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  34870. * All rights reserved.
  34871. *
  34872. * Redistribution and use in source and binary forms, with or without
  34873. * modification, are permitted provided that the following conditions are
  34874. * met:
  34875. *
  34876. * 1. Redistributions of source code must retain the above copyright
  34877. * notice, this list of conditions and the following disclaimer.
  34878. *
  34879. * 2. Redistributions in binary form must reproduce the above
  34880. * copyright notice, this list of conditions and the following
  34881. * disclaimer in the documentation and/or other materials provided
  34882. * with the distribution.
  34883. *
  34884. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  34885. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  34886. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  34887. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  34888. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  34889. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  34890. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  34891. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  34892. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  34893. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  34894. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34895. *
  34896. * The views and conclusions contained in the software and documentation
  34897. * are those of the authors and should not be interpreted as representing
  34898. * official policies, either expressed or implied, of the authors.
  34899. */
  34900. /**
  34901. * Expand the S-box tables.
  34902. *
  34903. * @private
  34904. */
  34905. var precompute = function precompute() {
  34906. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  34907. var encTable = tables[0];
  34908. var decTable = tables[1];
  34909. var sbox = encTable[4];
  34910. var sboxInv = decTable[4];
  34911. var i = void 0;
  34912. var x = void 0;
  34913. var xInv = void 0;
  34914. var d = [];
  34915. var th = [];
  34916. var x2 = void 0;
  34917. var x4 = void 0;
  34918. var x8 = void 0;
  34919. var s = void 0;
  34920. var tEnc = void 0;
  34921. var tDec = void 0; // Compute double and third tables
  34922. for (i = 0; i < 256; i++) {
  34923. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  34924. }
  34925. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  34926. // Compute sbox
  34927. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  34928. s = s >> 8 ^ s & 255 ^ 99;
  34929. sbox[x] = s;
  34930. sboxInv[s] = x; // Compute MixColumns
  34931. x8 = d[x4 = d[x2 = d[x]]];
  34932. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  34933. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  34934. for (i = 0; i < 4; i++) {
  34935. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  34936. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  34937. }
  34938. } // Compactify. Considerable speedup on Firefox.
  34939. for (i = 0; i < 5; i++) {
  34940. encTable[i] = encTable[i].slice(0);
  34941. decTable[i] = decTable[i].slice(0);
  34942. }
  34943. return tables;
  34944. };
  34945. var aesTables = null;
  34946. /**
  34947. * Schedule out an AES key for both encryption and decryption. This
  34948. * is a low-level class. Use a cipher mode to do bulk encryption.
  34949. *
  34950. * @class AES
  34951. * @param key {Array} The key as an array of 4, 6 or 8 words.
  34952. */
  34953. var AES = function () {
  34954. function AES(key) {
  34955. classCallCheck(this, AES);
  34956. /**
  34957. * The expanded S-box and inverse S-box tables. These will be computed
  34958. * on the client so that we don't have to send them down the wire.
  34959. *
  34960. * There are two tables, _tables[0] is for encryption and
  34961. * _tables[1] is for decryption.
  34962. *
  34963. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  34964. * last (_tables[01][4]) is the S-box itself.
  34965. *
  34966. * @private
  34967. */
  34968. // if we have yet to precompute the S-box tables
  34969. // do so now
  34970. if (!aesTables) {
  34971. aesTables = precompute();
  34972. } // then make a copy of that object for use
  34973. 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()]];
  34974. var i = void 0;
  34975. var j = void 0;
  34976. var tmp = void 0;
  34977. var encKey = void 0;
  34978. var decKey = void 0;
  34979. var sbox = this._tables[0][4];
  34980. var decTable = this._tables[1];
  34981. var keyLen = key.length;
  34982. var rcon = 1;
  34983. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  34984. throw new Error('Invalid aes key size');
  34985. }
  34986. encKey = key.slice(0);
  34987. decKey = [];
  34988. this._key = [encKey, decKey]; // schedule encryption keys
  34989. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  34990. tmp = encKey[i - 1]; // apply sbox
  34991. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  34992. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  34993. if (i % keyLen === 0) {
  34994. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  34995. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  34996. }
  34997. }
  34998. encKey[i] = encKey[i - keyLen] ^ tmp;
  34999. } // schedule decryption keys
  35000. for (j = 0; i; j++, i--) {
  35001. tmp = encKey[j & 3 ? i : i - 4];
  35002. if (i <= 4 || j < 4) {
  35003. decKey[j] = tmp;
  35004. } else {
  35005. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  35006. }
  35007. }
  35008. }
  35009. /**
  35010. * Decrypt 16 bytes, specified as four 32-bit words.
  35011. *
  35012. * @param {Number} encrypted0 the first word to decrypt
  35013. * @param {Number} encrypted1 the second word to decrypt
  35014. * @param {Number} encrypted2 the third word to decrypt
  35015. * @param {Number} encrypted3 the fourth word to decrypt
  35016. * @param {Int32Array} out the array to write the decrypted words
  35017. * into
  35018. * @param {Number} offset the offset into the output array to start
  35019. * writing results
  35020. * @return {Array} The plaintext.
  35021. */
  35022. AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  35023. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  35024. var a = encrypted0 ^ key[0];
  35025. var b = encrypted3 ^ key[1];
  35026. var c = encrypted2 ^ key[2];
  35027. var d = encrypted1 ^ key[3];
  35028. var a2 = void 0;
  35029. var b2 = void 0;
  35030. var c2 = void 0; // key.length === 2 ?
  35031. var nInnerRounds = key.length / 4 - 2;
  35032. var i = void 0;
  35033. var kIndex = 4;
  35034. var table = this._tables[1]; // load up the tables
  35035. var table0 = table[0];
  35036. var table1 = table[1];
  35037. var table2 = table[2];
  35038. var table3 = table[3];
  35039. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  35040. for (i = 0; i < nInnerRounds; i++) {
  35041. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  35042. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  35043. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  35044. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  35045. kIndex += 4;
  35046. a = a2;
  35047. b = b2;
  35048. c = c2;
  35049. } // Last round.
  35050. for (i = 0; i < 4; i++) {
  35051. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  35052. a2 = a;
  35053. a = b;
  35054. b = c;
  35055. c = d;
  35056. d = a2;
  35057. }
  35058. };
  35059. return AES;
  35060. }();
  35061. /**
  35062. * @file stream.js
  35063. */
  35064. /**
  35065. * A lightweight readable stream implemention that handles event dispatching.
  35066. *
  35067. * @class Stream
  35068. */
  35069. var Stream = function () {
  35070. function Stream() {
  35071. classCallCheck(this, Stream);
  35072. this.listeners = {};
  35073. }
  35074. /**
  35075. * Add a listener for a specified event type.
  35076. *
  35077. * @param {String} type the event name
  35078. * @param {Function} listener the callback to be invoked when an event of
  35079. * the specified type occurs
  35080. */
  35081. Stream.prototype.on = function on(type, listener) {
  35082. if (!this.listeners[type]) {
  35083. this.listeners[type] = [];
  35084. }
  35085. this.listeners[type].push(listener);
  35086. };
  35087. /**
  35088. * Remove a listener for a specified event type.
  35089. *
  35090. * @param {String} type the event name
  35091. * @param {Function} listener a function previously registered for this
  35092. * type of event through `on`
  35093. * @return {Boolean} if we could turn it off or not
  35094. */
  35095. Stream.prototype.off = function off(type, listener) {
  35096. if (!this.listeners[type]) {
  35097. return false;
  35098. }
  35099. var index = this.listeners[type].indexOf(listener);
  35100. this.listeners[type].splice(index, 1);
  35101. return index > -1;
  35102. };
  35103. /**
  35104. * Trigger an event of the specified type on this stream. Any additional
  35105. * arguments to this function are passed as parameters to event listeners.
  35106. *
  35107. * @param {String} type the event name
  35108. */
  35109. Stream.prototype.trigger = function trigger(type) {
  35110. var callbacks = this.listeners[type];
  35111. if (!callbacks) {
  35112. return;
  35113. } // Slicing the arguments on every invocation of this method
  35114. // can add a significant amount of overhead. Avoid the
  35115. // intermediate object creation for the common case of a
  35116. // single callback argument
  35117. if (arguments.length === 2) {
  35118. var length = callbacks.length;
  35119. for (var i = 0; i < length; ++i) {
  35120. callbacks[i].call(this, arguments[1]);
  35121. }
  35122. } else {
  35123. var args = Array.prototype.slice.call(arguments, 1);
  35124. var _length = callbacks.length;
  35125. for (var _i = 0; _i < _length; ++_i) {
  35126. callbacks[_i].apply(this, args);
  35127. }
  35128. }
  35129. };
  35130. /**
  35131. * Destroys the stream and cleans up.
  35132. */
  35133. Stream.prototype.dispose = function dispose() {
  35134. this.listeners = {};
  35135. };
  35136. /**
  35137. * Forwards all `data` events on this stream to the destination stream. The
  35138. * destination stream should provide a method `push` to receive the data
  35139. * events as they arrive.
  35140. *
  35141. * @param {Stream} destination the stream that will receive all `data` events
  35142. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  35143. */
  35144. Stream.prototype.pipe = function pipe(destination) {
  35145. this.on('data', function (data) {
  35146. destination.push(data);
  35147. });
  35148. };
  35149. return Stream;
  35150. }();
  35151. /**
  35152. * @file async-stream.js
  35153. */
  35154. /**
  35155. * A wrapper around the Stream class to use setTiemout
  35156. * and run stream "jobs" Asynchronously
  35157. *
  35158. * @class AsyncStream
  35159. * @extends Stream
  35160. */
  35161. var AsyncStream$$1 = function (_Stream) {
  35162. inherits(AsyncStream$$1, _Stream);
  35163. function AsyncStream$$1() {
  35164. classCallCheck(this, AsyncStream$$1);
  35165. var _this = possibleConstructorReturn(this, _Stream.call(this, Stream));
  35166. _this.jobs = [];
  35167. _this.delay = 1;
  35168. _this.timeout_ = null;
  35169. return _this;
  35170. }
  35171. /**
  35172. * process an async job
  35173. *
  35174. * @private
  35175. */
  35176. AsyncStream$$1.prototype.processJob_ = function processJob_() {
  35177. this.jobs.shift()();
  35178. if (this.jobs.length) {
  35179. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  35180. } else {
  35181. this.timeout_ = null;
  35182. }
  35183. };
  35184. /**
  35185. * push a job into the stream
  35186. *
  35187. * @param {Function} job the job to push into the stream
  35188. */
  35189. AsyncStream$$1.prototype.push = function push(job) {
  35190. this.jobs.push(job);
  35191. if (!this.timeout_) {
  35192. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  35193. }
  35194. };
  35195. return AsyncStream$$1;
  35196. }(Stream);
  35197. /**
  35198. * @file decrypter.js
  35199. *
  35200. * An asynchronous implementation of AES-128 CBC decryption with
  35201. * PKCS#7 padding.
  35202. */
  35203. /**
  35204. * Convert network-order (big-endian) bytes into their little-endian
  35205. * representation.
  35206. */
  35207. var ntoh = function ntoh(word) {
  35208. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  35209. };
  35210. /**
  35211. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  35212. *
  35213. * @param {Uint8Array} encrypted the encrypted bytes
  35214. * @param {Uint32Array} key the bytes of the decryption key
  35215. * @param {Uint32Array} initVector the initialization vector (IV) to
  35216. * use for the first round of CBC.
  35217. * @return {Uint8Array} the decrypted bytes
  35218. *
  35219. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  35220. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  35221. * @see https://tools.ietf.org/html/rfc2315
  35222. */
  35223. var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
  35224. // word-level access to the encrypted bytes
  35225. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  35226. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  35227. var decrypted = new Uint8Array(encrypted.byteLength);
  35228. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  35229. // decrypted data
  35230. var init0 = void 0;
  35231. var init1 = void 0;
  35232. var init2 = void 0;
  35233. var init3 = void 0;
  35234. var encrypted0 = void 0;
  35235. var encrypted1 = void 0;
  35236. var encrypted2 = void 0;
  35237. var encrypted3 = void 0; // iteration variable
  35238. var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
  35239. // passed-in reference and easier access
  35240. init0 = initVector[0];
  35241. init1 = initVector[1];
  35242. init2 = initVector[2];
  35243. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  35244. // to each decrypted block
  35245. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  35246. // convert big-endian (network order) words into little-endian
  35247. // (javascript order)
  35248. encrypted0 = ntoh(encrypted32[wordIx]);
  35249. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  35250. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  35251. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  35252. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  35253. // plaintext
  35254. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  35255. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  35256. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  35257. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  35258. init0 = encrypted0;
  35259. init1 = encrypted1;
  35260. init2 = encrypted2;
  35261. init3 = encrypted3;
  35262. }
  35263. return decrypted;
  35264. };
  35265. /**
  35266. * The `Decrypter` class that manages decryption of AES
  35267. * data through `AsyncStream` objects and the `decrypt`
  35268. * function
  35269. *
  35270. * @param {Uint8Array} encrypted the encrypted bytes
  35271. * @param {Uint32Array} key the bytes of the decryption key
  35272. * @param {Uint32Array} initVector the initialization vector (IV) to
  35273. * @param {Function} done the function to run when done
  35274. * @class Decrypter
  35275. */
  35276. var Decrypter$$1 = function () {
  35277. function Decrypter$$1(encrypted, key, initVector, done) {
  35278. classCallCheck(this, Decrypter$$1);
  35279. var step = Decrypter$$1.STEP;
  35280. var encrypted32 = new Int32Array(encrypted.buffer);
  35281. var decrypted = new Uint8Array(encrypted.byteLength);
  35282. var i = 0;
  35283. this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously
  35284. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  35285. for (i = step; i < encrypted32.length; i += step) {
  35286. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  35287. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  35288. } // invoke the done() callback when everything is finished
  35289. this.asyncStream_.push(function () {
  35290. // remove pkcs#7 padding from the decrypted bytes
  35291. done(null, unpad(decrypted));
  35292. });
  35293. }
  35294. /**
  35295. * a getter for step the maximum number of bytes to process at one time
  35296. *
  35297. * @return {Number} the value of step 32000
  35298. */
  35299. /**
  35300. * @private
  35301. */
  35302. Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  35303. return function () {
  35304. var bytes = decrypt$$1(encrypted, key, initVector);
  35305. decrypted.set(bytes, encrypted.byteOffset);
  35306. };
  35307. };
  35308. createClass(Decrypter$$1, null, [{
  35309. key: 'STEP',
  35310. get: function get$$1() {
  35311. // 4 * 8000;
  35312. return 32000;
  35313. }
  35314. }]);
  35315. return Decrypter$$1;
  35316. }();
  35317. /**
  35318. * @file bin-utils.js
  35319. */
  35320. /**
  35321. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  35322. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  35323. *
  35324. * @param {Object} message
  35325. * Object of properties and values to send to the web worker
  35326. * @return {Object}
  35327. * Modified message with TypedArray values expanded
  35328. * @function createTransferableMessage
  35329. */
  35330. var createTransferableMessage = function createTransferableMessage(message) {
  35331. var transferable = {};
  35332. Object.keys(message).forEach(function (key) {
  35333. var value = message[key];
  35334. if (ArrayBuffer.isView(value)) {
  35335. transferable[key] = {
  35336. bytes: value.buffer,
  35337. byteOffset: value.byteOffset,
  35338. byteLength: value.byteLength
  35339. };
  35340. } else {
  35341. transferable[key] = value;
  35342. }
  35343. });
  35344. return transferable;
  35345. };
  35346. /**
  35347. * Our web worker interface so that things can talk to aes-decrypter
  35348. * that will be running in a web worker. the scope is passed to this by
  35349. * webworkify.
  35350. *
  35351. * @param {Object} self
  35352. * the scope for the web worker
  35353. */
  35354. var DecrypterWorker = function DecrypterWorker(self) {
  35355. self.onmessage = function (event) {
  35356. var data = event.data;
  35357. var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
  35358. var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
  35359. var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
  35360. /* eslint-disable no-new, handle-callback-err */
  35361. new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
  35362. self.postMessage(createTransferableMessage({
  35363. source: data.source,
  35364. decrypted: bytes
  35365. }), [bytes.buffer]);
  35366. });
  35367. /* eslint-enable */
  35368. };
  35369. };
  35370. var decrypterWorker = new DecrypterWorker(self);
  35371. return decrypterWorker;
  35372. }();
  35373. });
  35374. /**
  35375. * Convert the properties of an HLS track into an audioTrackKind.
  35376. *
  35377. * @private
  35378. */
  35379. var audioTrackKind_ = function audioTrackKind_(properties) {
  35380. var kind = properties.default ? 'main' : 'alternative';
  35381. if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
  35382. kind = 'main-desc';
  35383. }
  35384. return kind;
  35385. };
  35386. /**
  35387. * Pause provided segment loader and playlist loader if active
  35388. *
  35389. * @param {SegmentLoader} segmentLoader
  35390. * SegmentLoader to pause
  35391. * @param {Object} mediaType
  35392. * Active media type
  35393. * @function stopLoaders
  35394. */
  35395. var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
  35396. segmentLoader.abort();
  35397. segmentLoader.pause();
  35398. if (mediaType && mediaType.activePlaylistLoader) {
  35399. mediaType.activePlaylistLoader.pause();
  35400. mediaType.activePlaylistLoader = null;
  35401. }
  35402. };
  35403. /**
  35404. * Start loading provided segment loader and playlist loader
  35405. *
  35406. * @param {PlaylistLoader} playlistLoader
  35407. * PlaylistLoader to start loading
  35408. * @param {Object} mediaType
  35409. * Active media type
  35410. * @function startLoaders
  35411. */
  35412. var startLoaders = function startLoaders(playlistLoader, mediaType) {
  35413. // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
  35414. // playlist loader
  35415. mediaType.activePlaylistLoader = playlistLoader;
  35416. playlistLoader.load();
  35417. };
  35418. /**
  35419. * Returns a function to be called when the media group changes. It performs a
  35420. * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
  35421. * change of group is merely a rendition switch of the same content at another encoding,
  35422. * rather than a change of content, such as switching audio from English to Spanish.
  35423. *
  35424. * @param {String} type
  35425. * MediaGroup type
  35426. * @param {Object} settings
  35427. * Object containing required information for media groups
  35428. * @return {Function}
  35429. * Handler for a non-destructive resync of SegmentLoader when the active media
  35430. * group changes.
  35431. * @function onGroupChanged
  35432. */
  35433. var onGroupChanged = function onGroupChanged(type, settings) {
  35434. return function () {
  35435. var _settings$segmentLoad = settings.segmentLoaders,
  35436. segmentLoader = _settings$segmentLoad[type],
  35437. mainSegmentLoader = _settings$segmentLoad.main,
  35438. mediaType = settings.mediaTypes[type];
  35439. var activeTrack = mediaType.activeTrack();
  35440. var activeGroup = mediaType.activeGroup(activeTrack);
  35441. var previousActiveLoader = mediaType.activePlaylistLoader;
  35442. stopLoaders(segmentLoader, mediaType);
  35443. if (!activeGroup) {
  35444. // there is no group active
  35445. return;
  35446. }
  35447. if (!activeGroup.playlistLoader) {
  35448. if (previousActiveLoader) {
  35449. // The previous group had a playlist loader but the new active group does not
  35450. // this means we are switching from demuxed to muxed audio. In this case we want to
  35451. // do a destructive reset of the main segment loader and not restart the audio
  35452. // loaders.
  35453. mainSegmentLoader.resetEverything();
  35454. }
  35455. return;
  35456. } // Non-destructive resync
  35457. segmentLoader.resyncLoader();
  35458. startLoaders(activeGroup.playlistLoader, mediaType);
  35459. };
  35460. };
  35461. /**
  35462. * Returns a function to be called when the media track changes. It performs a
  35463. * destructive reset of the SegmentLoader to ensure we start loading as close to
  35464. * currentTime as possible.
  35465. *
  35466. * @param {String} type
  35467. * MediaGroup type
  35468. * @param {Object} settings
  35469. * Object containing required information for media groups
  35470. * @return {Function}
  35471. * Handler for a destructive reset of SegmentLoader when the active media
  35472. * track changes.
  35473. * @function onTrackChanged
  35474. */
  35475. var onTrackChanged = function onTrackChanged(type, settings) {
  35476. return function () {
  35477. var _settings$segmentLoad2 = settings.segmentLoaders,
  35478. segmentLoader = _settings$segmentLoad2[type],
  35479. mainSegmentLoader = _settings$segmentLoad2.main,
  35480. mediaType = settings.mediaTypes[type];
  35481. var activeTrack = mediaType.activeTrack();
  35482. var activeGroup = mediaType.activeGroup(activeTrack);
  35483. var previousActiveLoader = mediaType.activePlaylistLoader;
  35484. stopLoaders(segmentLoader, mediaType);
  35485. if (!activeGroup) {
  35486. // there is no group active so we do not want to restart loaders
  35487. return;
  35488. }
  35489. if (!activeGroup.playlistLoader) {
  35490. // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
  35491. // loader for the audio group), we want to do a destructive reset of the main segment
  35492. // loader and not restart the audio loaders
  35493. mainSegmentLoader.resetEverything();
  35494. return;
  35495. }
  35496. if (previousActiveLoader === activeGroup.playlistLoader) {
  35497. // Nothing has actually changed. This can happen because track change events can fire
  35498. // multiple times for a "single" change. One for enabling the new active track, and
  35499. // one for disabling the track that was active
  35500. startLoaders(activeGroup.playlistLoader, mediaType);
  35501. return;
  35502. }
  35503. if (segmentLoader.track) {
  35504. // For WebVTT, set the new text track in the segmentloader
  35505. segmentLoader.track(activeTrack);
  35506. } // destructive reset
  35507. segmentLoader.resetEverything();
  35508. startLoaders(activeGroup.playlistLoader, mediaType);
  35509. };
  35510. };
  35511. var onError = {
  35512. /**
  35513. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  35514. * an error.
  35515. *
  35516. * @param {String} type
  35517. * MediaGroup type
  35518. * @param {Object} settings
  35519. * Object containing required information for media groups
  35520. * @return {Function}
  35521. * Error handler. Logs warning (or error if the playlist is blacklisted) to
  35522. * console and switches back to default audio track.
  35523. * @function onError.AUDIO
  35524. */
  35525. AUDIO: function AUDIO(type, settings) {
  35526. return function () {
  35527. var segmentLoader = settings.segmentLoaders[type],
  35528. mediaType = settings.mediaTypes[type],
  35529. blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
  35530. stopLoaders(segmentLoader, mediaType); // switch back to default audio track
  35531. var activeTrack = mediaType.activeTrack();
  35532. var activeGroup = mediaType.activeGroup();
  35533. var id = (activeGroup.filter(function (group) {
  35534. return group.default;
  35535. })[0] || activeGroup[0]).id;
  35536. var defaultTrack = mediaType.tracks[id];
  35537. if (activeTrack === defaultTrack) {
  35538. // Default track encountered an error. All we can do now is blacklist the current
  35539. // rendition and hope another will switch audio groups
  35540. blacklistCurrentPlaylist({
  35541. message: 'Problem encountered loading the default audio track.'
  35542. });
  35543. return;
  35544. }
  35545. videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
  35546. for (var trackId in mediaType.tracks) {
  35547. mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
  35548. }
  35549. mediaType.onTrackChanged();
  35550. };
  35551. },
  35552. /**
  35553. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  35554. * an error.
  35555. *
  35556. * @param {String} type
  35557. * MediaGroup type
  35558. * @param {Object} settings
  35559. * Object containing required information for media groups
  35560. * @return {Function}
  35561. * Error handler. Logs warning to console and disables the active subtitle track
  35562. * @function onError.SUBTITLES
  35563. */
  35564. SUBTITLES: function SUBTITLES(type, settings) {
  35565. return function () {
  35566. var segmentLoader = settings.segmentLoaders[type],
  35567. mediaType = settings.mediaTypes[type];
  35568. videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
  35569. stopLoaders(segmentLoader, mediaType);
  35570. var track = mediaType.activeTrack();
  35571. if (track) {
  35572. track.mode = 'disabled';
  35573. }
  35574. mediaType.onTrackChanged();
  35575. };
  35576. }
  35577. };
  35578. var setupListeners = {
  35579. /**
  35580. * Setup event listeners for audio playlist loader
  35581. *
  35582. * @param {String} type
  35583. * MediaGroup type
  35584. * @param {PlaylistLoader|null} playlistLoader
  35585. * PlaylistLoader to register listeners on
  35586. * @param {Object} settings
  35587. * Object containing required information for media groups
  35588. * @function setupListeners.AUDIO
  35589. */
  35590. AUDIO: function AUDIO(type, playlistLoader, settings) {
  35591. if (!playlistLoader) {
  35592. // no playlist loader means audio will be muxed with the video
  35593. return;
  35594. }
  35595. var tech = settings.tech,
  35596. requestOptions = settings.requestOptions,
  35597. segmentLoader = settings.segmentLoaders[type];
  35598. playlistLoader.on('loadedmetadata', function () {
  35599. var media = playlistLoader.media();
  35600. segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
  35601. // permits, start downloading segments
  35602. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  35603. segmentLoader.load();
  35604. }
  35605. });
  35606. playlistLoader.on('loadedplaylist', function () {
  35607. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  35608. if (!tech.paused()) {
  35609. segmentLoader.load();
  35610. }
  35611. });
  35612. playlistLoader.on('error', onError[type](type, settings));
  35613. },
  35614. /**
  35615. * Setup event listeners for subtitle playlist loader
  35616. *
  35617. * @param {String} type
  35618. * MediaGroup type
  35619. * @param {PlaylistLoader|null} playlistLoader
  35620. * PlaylistLoader to register listeners on
  35621. * @param {Object} settings
  35622. * Object containing required information for media groups
  35623. * @function setupListeners.SUBTITLES
  35624. */
  35625. SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
  35626. var tech = settings.tech,
  35627. requestOptions = settings.requestOptions,
  35628. segmentLoader = settings.segmentLoaders[type],
  35629. mediaType = settings.mediaTypes[type];
  35630. playlistLoader.on('loadedmetadata', function () {
  35631. var media = playlistLoader.media();
  35632. segmentLoader.playlist(media, requestOptions);
  35633. segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
  35634. // permits, start downloading segments
  35635. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  35636. segmentLoader.load();
  35637. }
  35638. });
  35639. playlistLoader.on('loadedplaylist', function () {
  35640. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  35641. if (!tech.paused()) {
  35642. segmentLoader.load();
  35643. }
  35644. });
  35645. playlistLoader.on('error', onError[type](type, settings));
  35646. }
  35647. };
  35648. var byGroupId = function byGroupId(type, groupId) {
  35649. return function (playlist) {
  35650. return playlist.attributes[type] === groupId;
  35651. };
  35652. };
  35653. var byResolvedUri = function byResolvedUri(resolvedUri) {
  35654. return function (playlist) {
  35655. return playlist.resolvedUri === resolvedUri;
  35656. };
  35657. };
  35658. var initialize = {
  35659. /**
  35660. * Setup PlaylistLoaders and AudioTracks for the audio groups
  35661. *
  35662. * @param {String} type
  35663. * MediaGroup type
  35664. * @param {Object} settings
  35665. * Object containing required information for media groups
  35666. * @function initialize.AUDIO
  35667. */
  35668. 'AUDIO': function AUDIO(type, settings) {
  35669. var hls = settings.hls,
  35670. sourceType = settings.sourceType,
  35671. segmentLoader = settings.segmentLoaders[type],
  35672. requestOptions = settings.requestOptions,
  35673. _settings$master = settings.master,
  35674. mediaGroups = _settings$master.mediaGroups,
  35675. playlists = _settings$master.playlists,
  35676. _settings$mediaTypes$ = settings.mediaTypes[type],
  35677. groups = _settings$mediaTypes$.groups,
  35678. tracks = _settings$mediaTypes$.tracks,
  35679. masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none
  35680. if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
  35681. mediaGroups[type] = {
  35682. main: {
  35683. default: {
  35684. default: true
  35685. }
  35686. }
  35687. };
  35688. }
  35689. for (var groupId in mediaGroups[type]) {
  35690. if (!groups[groupId]) {
  35691. groups[groupId] = [];
  35692. } // List of playlists that have an AUDIO attribute value matching the current
  35693. // group ID
  35694. var groupPlaylists = playlists.filter(byGroupId(type, groupId));
  35695. for (var variantLabel in mediaGroups[type][groupId]) {
  35696. var properties = mediaGroups[type][groupId][variantLabel]; // List of playlists for the current group ID that have a matching uri with
  35697. // this alternate audio variant
  35698. var matchingPlaylists = groupPlaylists.filter(byResolvedUri(properties.resolvedUri));
  35699. if (matchingPlaylists.length) {
  35700. // If there is a playlist that has the same uri as this audio variant, assume
  35701. // that the playlist is audio only. We delete the resolvedUri property here
  35702. // to prevent a playlist loader from being created so that we don't have
  35703. // both the main and audio segment loaders loading the same audio segments
  35704. // from the same playlist.
  35705. delete properties.resolvedUri;
  35706. }
  35707. var playlistLoader = void 0;
  35708. if (properties.resolvedUri) {
  35709. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  35710. } else if (properties.playlists && sourceType === 'dash') {
  35711. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  35712. } else {
  35713. // no resolvedUri means the audio is muxed with the video when using this
  35714. // audio track
  35715. playlistLoader = null;
  35716. }
  35717. properties = videojs$1.mergeOptions({
  35718. id: variantLabel,
  35719. playlistLoader: playlistLoader
  35720. }, properties);
  35721. setupListeners[type](type, properties.playlistLoader, settings);
  35722. groups[groupId].push(properties);
  35723. if (typeof tracks[variantLabel] === 'undefined') {
  35724. var track = new videojs$1.AudioTrack({
  35725. id: variantLabel,
  35726. kind: audioTrackKind_(properties),
  35727. enabled: false,
  35728. language: properties.language,
  35729. default: properties.default,
  35730. label: variantLabel
  35731. });
  35732. tracks[variantLabel] = track;
  35733. }
  35734. }
  35735. } // setup single error event handler for the segment loader
  35736. segmentLoader.on('error', onError[type](type, settings));
  35737. },
  35738. /**
  35739. * Setup PlaylistLoaders and TextTracks for the subtitle groups
  35740. *
  35741. * @param {String} type
  35742. * MediaGroup type
  35743. * @param {Object} settings
  35744. * Object containing required information for media groups
  35745. * @function initialize.SUBTITLES
  35746. */
  35747. 'SUBTITLES': function SUBTITLES(type, settings) {
  35748. var tech = settings.tech,
  35749. hls = settings.hls,
  35750. sourceType = settings.sourceType,
  35751. segmentLoader = settings.segmentLoaders[type],
  35752. requestOptions = settings.requestOptions,
  35753. mediaGroups = settings.master.mediaGroups,
  35754. _settings$mediaTypes$2 = settings.mediaTypes[type],
  35755. groups = _settings$mediaTypes$2.groups,
  35756. tracks = _settings$mediaTypes$2.tracks,
  35757. masterPlaylistLoader = settings.masterPlaylistLoader;
  35758. for (var groupId in mediaGroups[type]) {
  35759. if (!groups[groupId]) {
  35760. groups[groupId] = [];
  35761. }
  35762. for (var variantLabel in mediaGroups[type][groupId]) {
  35763. if (mediaGroups[type][groupId][variantLabel].forced) {
  35764. // Subtitle playlists with the forced attribute are not selectable in Safari.
  35765. // According to Apple's HLS Authoring Specification:
  35766. // If content has forced subtitles and regular subtitles in a given language,
  35767. // the regular subtitles track in that language MUST contain both the forced
  35768. // subtitles and the regular subtitles for that language.
  35769. // Because of this requirement and that Safari does not add forced subtitles,
  35770. // forced subtitles are skipped here to maintain consistent experience across
  35771. // all platforms
  35772. continue;
  35773. }
  35774. var properties = mediaGroups[type][groupId][variantLabel];
  35775. var playlistLoader = void 0;
  35776. if (sourceType === 'hls') {
  35777. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  35778. } else if (sourceType === 'dash') {
  35779. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  35780. }
  35781. properties = videojs$1.mergeOptions({
  35782. id: variantLabel,
  35783. playlistLoader: playlistLoader
  35784. }, properties);
  35785. setupListeners[type](type, properties.playlistLoader, settings);
  35786. groups[groupId].push(properties);
  35787. if (typeof tracks[variantLabel] === 'undefined') {
  35788. var track = tech.addRemoteTextTrack({
  35789. id: variantLabel,
  35790. kind: 'subtitles',
  35791. default: properties.default && properties.autoselect,
  35792. language: properties.language,
  35793. label: variantLabel
  35794. }, false).track;
  35795. tracks[variantLabel] = track;
  35796. }
  35797. }
  35798. } // setup single error event handler for the segment loader
  35799. segmentLoader.on('error', onError[type](type, settings));
  35800. },
  35801. /**
  35802. * Setup TextTracks for the closed-caption groups
  35803. *
  35804. * @param {String} type
  35805. * MediaGroup type
  35806. * @param {Object} settings
  35807. * Object containing required information for media groups
  35808. * @function initialize['CLOSED-CAPTIONS']
  35809. */
  35810. 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
  35811. var tech = settings.tech,
  35812. mediaGroups = settings.master.mediaGroups,
  35813. _settings$mediaTypes$3 = settings.mediaTypes[type],
  35814. groups = _settings$mediaTypes$3.groups,
  35815. tracks = _settings$mediaTypes$3.tracks;
  35816. for (var groupId in mediaGroups[type]) {
  35817. if (!groups[groupId]) {
  35818. groups[groupId] = [];
  35819. }
  35820. for (var variantLabel in mediaGroups[type][groupId]) {
  35821. var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that
  35822. // doesn't use a CCx INSTREAM-ID
  35823. if (!properties.instreamId.match(/CC\d/)) {
  35824. continue;
  35825. } // No PlaylistLoader is required for Closed-Captions because the captions are
  35826. // embedded within the video stream
  35827. groups[groupId].push(videojs$1.mergeOptions({
  35828. id: variantLabel
  35829. }, properties));
  35830. if (typeof tracks[variantLabel] === 'undefined') {
  35831. var track = tech.addRemoteTextTrack({
  35832. id: properties.instreamId,
  35833. kind: 'captions',
  35834. default: properties.default && properties.autoselect,
  35835. language: properties.language,
  35836. label: variantLabel
  35837. }, false).track;
  35838. tracks[variantLabel] = track;
  35839. }
  35840. }
  35841. }
  35842. }
  35843. };
  35844. /**
  35845. * Returns a function used to get the active group of the provided type
  35846. *
  35847. * @param {String} type
  35848. * MediaGroup type
  35849. * @param {Object} settings
  35850. * Object containing required information for media groups
  35851. * @return {Function}
  35852. * Function that returns the active media group for the provided type. Takes an
  35853. * optional parameter {TextTrack} track. If no track is provided, a list of all
  35854. * variants in the group, otherwise the variant corresponding to the provided
  35855. * track is returned.
  35856. * @function activeGroup
  35857. */
  35858. var activeGroup = function activeGroup(type, settings) {
  35859. return function (track) {
  35860. var masterPlaylistLoader = settings.masterPlaylistLoader,
  35861. groups = settings.mediaTypes[type].groups;
  35862. var media = masterPlaylistLoader.media();
  35863. if (!media) {
  35864. return null;
  35865. }
  35866. var variants = null;
  35867. if (media.attributes[type]) {
  35868. variants = groups[media.attributes[type]];
  35869. }
  35870. variants = variants || groups.main;
  35871. if (typeof track === 'undefined') {
  35872. return variants;
  35873. }
  35874. if (track === null) {
  35875. // An active track was specified so a corresponding group is expected. track === null
  35876. // means no track is currently active so there is no corresponding group
  35877. return null;
  35878. }
  35879. return variants.filter(function (props) {
  35880. return props.id === track.id;
  35881. })[0] || null;
  35882. };
  35883. };
  35884. var activeTrack = {
  35885. /**
  35886. * Returns a function used to get the active track of type provided
  35887. *
  35888. * @param {String} type
  35889. * MediaGroup type
  35890. * @param {Object} settings
  35891. * Object containing required information for media groups
  35892. * @return {Function}
  35893. * Function that returns the active media track for the provided type. Returns
  35894. * null if no track is active
  35895. * @function activeTrack.AUDIO
  35896. */
  35897. AUDIO: function AUDIO(type, settings) {
  35898. return function () {
  35899. var tracks = settings.mediaTypes[type].tracks;
  35900. for (var id in tracks) {
  35901. if (tracks[id].enabled) {
  35902. return tracks[id];
  35903. }
  35904. }
  35905. return null;
  35906. };
  35907. },
  35908. /**
  35909. * Returns a function used to get the active track of type provided
  35910. *
  35911. * @param {String} type
  35912. * MediaGroup type
  35913. * @param {Object} settings
  35914. * Object containing required information for media groups
  35915. * @return {Function}
  35916. * Function that returns the active media track for the provided type. Returns
  35917. * null if no track is active
  35918. * @function activeTrack.SUBTITLES
  35919. */
  35920. SUBTITLES: function SUBTITLES(type, settings) {
  35921. return function () {
  35922. var tracks = settings.mediaTypes[type].tracks;
  35923. for (var id in tracks) {
  35924. if (tracks[id].mode === 'showing') {
  35925. return tracks[id];
  35926. }
  35927. }
  35928. return null;
  35929. };
  35930. }
  35931. };
  35932. /**
  35933. * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
  35934. * Closed-Captions) specified in the master manifest.
  35935. *
  35936. * @param {Object} settings
  35937. * Object containing required information for setting up the media groups
  35938. * @param {SegmentLoader} settings.segmentLoaders.AUDIO
  35939. * Audio segment loader
  35940. * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
  35941. * Subtitle segment loader
  35942. * @param {SegmentLoader} settings.segmentLoaders.main
  35943. * Main segment loader
  35944. * @param {Tech} settings.tech
  35945. * The tech of the player
  35946. * @param {Object} settings.requestOptions
  35947. * XHR request options used by the segment loaders
  35948. * @param {PlaylistLoader} settings.masterPlaylistLoader
  35949. * PlaylistLoader for the master source
  35950. * @param {HlsHandler} settings.hls
  35951. * HLS SourceHandler
  35952. * @param {Object} settings.master
  35953. * The parsed master manifest
  35954. * @param {Object} settings.mediaTypes
  35955. * Object to store the loaders, tracks, and utility methods for each media type
  35956. * @param {Function} settings.blacklistCurrentPlaylist
  35957. * Blacklists the current rendition and forces a rendition switch.
  35958. * @function setupMediaGroups
  35959. */
  35960. var setupMediaGroups = function setupMediaGroups(settings) {
  35961. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  35962. initialize[type](type, settings);
  35963. });
  35964. var mediaTypes = settings.mediaTypes,
  35965. masterPlaylistLoader = settings.masterPlaylistLoader,
  35966. tech = settings.tech,
  35967. hls = settings.hls; // setup active group and track getters and change event handlers
  35968. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  35969. mediaTypes[type].activeGroup = activeGroup(type, settings);
  35970. mediaTypes[type].activeTrack = activeTrack[type](type, settings);
  35971. mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
  35972. mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
  35973. }); // DO NOT enable the default subtitle or caption track.
  35974. // DO enable the default audio track
  35975. var audioGroup = mediaTypes.AUDIO.activeGroup();
  35976. var groupId = (audioGroup.filter(function (group) {
  35977. return group.default;
  35978. })[0] || audioGroup[0]).id;
  35979. mediaTypes.AUDIO.tracks[groupId].enabled = true;
  35980. mediaTypes.AUDIO.onTrackChanged();
  35981. masterPlaylistLoader.on('mediachange', function () {
  35982. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  35983. return mediaTypes[type].onGroupChanged();
  35984. });
  35985. }); // custom audio track change event handler for usage event
  35986. var onAudioTrackChanged = function onAudioTrackChanged() {
  35987. mediaTypes.AUDIO.onTrackChanged();
  35988. tech.trigger({
  35989. type: 'usage',
  35990. name: 'hls-audio-change'
  35991. });
  35992. };
  35993. tech.audioTracks().addEventListener('change', onAudioTrackChanged);
  35994. tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  35995. hls.on('dispose', function () {
  35996. tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
  35997. tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  35998. }); // clear existing audio tracks and add the ones we just created
  35999. tech.clearTracks('audio');
  36000. for (var id in mediaTypes.AUDIO.tracks) {
  36001. tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
  36002. }
  36003. };
  36004. /**
  36005. * Creates skeleton object used to store the loaders, tracks, and utility methods for each
  36006. * media type
  36007. *
  36008. * @return {Object}
  36009. * Object to store the loaders, tracks, and utility methods for each media type
  36010. * @function createMediaTypes
  36011. */
  36012. var createMediaTypes = function createMediaTypes() {
  36013. var mediaTypes = {};
  36014. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  36015. mediaTypes[type] = {
  36016. groups: {},
  36017. tracks: {},
  36018. activePlaylistLoader: null,
  36019. activeGroup: noop,
  36020. activeTrack: noop,
  36021. onGroupChanged: noop,
  36022. onTrackChanged: noop
  36023. };
  36024. });
  36025. return mediaTypes;
  36026. };
  36027. /**
  36028. * @file master-playlist-controller.js
  36029. */
  36030. var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
  36031. var Hls = void 0; // SegmentLoader stats that need to have each loader's
  36032. // values summed to calculate the final value
  36033. var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
  36034. var sumLoaderStat = function sumLoaderStat(stat) {
  36035. return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
  36036. };
  36037. /**
  36038. * the master playlist controller controller all interactons
  36039. * between playlists and segmentloaders. At this time this mainly
  36040. * involves a master playlist and a series of audio playlists
  36041. * if they are available
  36042. *
  36043. * @class MasterPlaylistController
  36044. * @extends videojs.EventTarget
  36045. */
  36046. var MasterPlaylistController = function (_videojs$EventTarget) {
  36047. inherits(MasterPlaylistController, _videojs$EventTarget);
  36048. function MasterPlaylistController(options) {
  36049. classCallCheck(this, MasterPlaylistController);
  36050. var _this = possibleConstructorReturn(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
  36051. var url = options.url,
  36052. handleManifestRedirects = options.handleManifestRedirects,
  36053. withCredentials = options.withCredentials,
  36054. tech = options.tech,
  36055. bandwidth = options.bandwidth,
  36056. externHls = options.externHls,
  36057. useCueTags = options.useCueTags,
  36058. blacklistDuration = options.blacklistDuration,
  36059. enableLowInitialPlaylist = options.enableLowInitialPlaylist,
  36060. sourceType = options.sourceType,
  36061. seekTo = options.seekTo,
  36062. cacheEncryptionKeys = options.cacheEncryptionKeys;
  36063. if (!url) {
  36064. throw new Error('A non-empty playlist URL is required');
  36065. }
  36066. Hls = externHls;
  36067. _this.withCredentials = withCredentials;
  36068. _this.tech_ = tech;
  36069. _this.hls_ = tech.hls;
  36070. _this.seekTo_ = seekTo;
  36071. _this.sourceType_ = sourceType;
  36072. _this.useCueTags_ = useCueTags;
  36073. _this.blacklistDuration = blacklistDuration;
  36074. _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
  36075. if (_this.useCueTags_) {
  36076. _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
  36077. _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
  36078. }
  36079. _this.requestOptions_ = {
  36080. withCredentials: withCredentials,
  36081. handleManifestRedirects: handleManifestRedirects,
  36082. timeout: null
  36083. };
  36084. _this.mediaTypes_ = createMediaTypes();
  36085. _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player
  36086. _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
  36087. _this.seekable_ = videojs$1.createTimeRanges();
  36088. _this.hasPlayed_ = function () {
  36089. return false;
  36090. };
  36091. _this.syncController_ = new SyncController(options);
  36092. _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
  36093. kind: 'metadata',
  36094. label: 'segment-metadata'
  36095. }, false).track;
  36096. _this.decrypter_ = new Decrypter$1();
  36097. _this.inbandTextTracks_ = {};
  36098. var segmentLoaderSettings = {
  36099. hls: _this.hls_,
  36100. mediaSource: _this.mediaSource,
  36101. currentTime: _this.tech_.currentTime.bind(_this.tech_),
  36102. seekable: function seekable$$1() {
  36103. return _this.seekable();
  36104. },
  36105. seeking: function seeking() {
  36106. return _this.tech_.seeking();
  36107. },
  36108. duration: function duration$$1() {
  36109. return _this.mediaSource.duration;
  36110. },
  36111. hasPlayed: function hasPlayed() {
  36112. return _this.hasPlayed_();
  36113. },
  36114. goalBufferLength: function goalBufferLength() {
  36115. return _this.goalBufferLength();
  36116. },
  36117. bandwidth: bandwidth,
  36118. syncController: _this.syncController_,
  36119. decrypter: _this.decrypter_,
  36120. sourceType: _this.sourceType_,
  36121. inbandTextTracks: _this.inbandTextTracks_,
  36122. cacheEncryptionKeys: cacheEncryptionKeys
  36123. };
  36124. _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_);
  36125. _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
  36126. // combined audio/video or just video when alternate audio track is selected
  36127. _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  36128. segmentMetadataTrack: _this.segmentMetadataTrack_,
  36129. loaderType: 'main'
  36130. }), options); // alternate audio track
  36131. _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  36132. loaderType: 'audio'
  36133. }), options);
  36134. _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  36135. loaderType: 'vtt'
  36136. }), options);
  36137. _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters
  36138. loaderStats.forEach(function (stat) {
  36139. _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
  36140. });
  36141. _this.logger_ = logger('MPC');
  36142. _this.masterPlaylistLoader_.load();
  36143. return _this;
  36144. }
  36145. /**
  36146. * Register event handlers on the master playlist loader. A helper
  36147. * function for construction time.
  36148. *
  36149. * @private
  36150. */
  36151. createClass(MasterPlaylistController, [{
  36152. key: 'setupMasterPlaylistLoaderListeners_',
  36153. value: function setupMasterPlaylistLoaderListeners_() {
  36154. var _this2 = this;
  36155. this.masterPlaylistLoader_.on('loadedmetadata', function () {
  36156. var media = _this2.masterPlaylistLoader_.media();
  36157. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  36158. // timeout the request.
  36159. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  36160. _this2.requestOptions_.timeout = 0;
  36161. } else {
  36162. _this2.requestOptions_.timeout = requestTimeout;
  36163. } // if this isn't a live video and preload permits, start
  36164. // downloading segments
  36165. if (media.endList && _this2.tech_.preload() !== 'none') {
  36166. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  36167. _this2.mainSegmentLoader_.load();
  36168. }
  36169. setupMediaGroups({
  36170. sourceType: _this2.sourceType_,
  36171. segmentLoaders: {
  36172. AUDIO: _this2.audioSegmentLoader_,
  36173. SUBTITLES: _this2.subtitleSegmentLoader_,
  36174. main: _this2.mainSegmentLoader_
  36175. },
  36176. tech: _this2.tech_,
  36177. requestOptions: _this2.requestOptions_,
  36178. masterPlaylistLoader: _this2.masterPlaylistLoader_,
  36179. hls: _this2.hls_,
  36180. master: _this2.master(),
  36181. mediaTypes: _this2.mediaTypes_,
  36182. blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
  36183. });
  36184. _this2.triggerPresenceUsage_(_this2.master(), media);
  36185. try {
  36186. _this2.setupSourceBuffers_();
  36187. } catch (e) {
  36188. videojs$1.log.warn('Failed to create SourceBuffers', e);
  36189. return _this2.mediaSource.endOfStream('decode');
  36190. }
  36191. _this2.setupFirstPlay();
  36192. if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
  36193. _this2.trigger('selectedinitialmedia');
  36194. } else {
  36195. // We must wait for the active audio playlist loader to
  36196. // finish setting up before triggering this event so the
  36197. // representations API and EME setup is correct
  36198. _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
  36199. _this2.trigger('selectedinitialmedia');
  36200. });
  36201. }
  36202. });
  36203. this.masterPlaylistLoader_.on('loadedplaylist', function () {
  36204. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  36205. if (!updatedPlaylist) {
  36206. // blacklist any variants that are not supported by the browser before selecting
  36207. // an initial media as the playlist selectors do not consider browser support
  36208. _this2.excludeUnsupportedVariants_();
  36209. var selectedMedia = void 0;
  36210. if (_this2.enableLowInitialPlaylist) {
  36211. selectedMedia = _this2.selectInitialPlaylist();
  36212. }
  36213. if (!selectedMedia) {
  36214. selectedMedia = _this2.selectPlaylist();
  36215. }
  36216. _this2.initialMedia_ = selectedMedia;
  36217. _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
  36218. return;
  36219. }
  36220. if (_this2.useCueTags_) {
  36221. _this2.updateAdCues_(updatedPlaylist);
  36222. } // TODO: Create a new event on the PlaylistLoader that signals
  36223. // that the segments have changed in some way and use that to
  36224. // update the SegmentLoader instead of doing it twice here and
  36225. // on `mediachange`
  36226. _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
  36227. _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running,
  36228. // as it is possible that it was temporarily stopped while waiting for
  36229. // a playlist (e.g., in case the playlist errored and we re-requested it).
  36230. if (!_this2.tech_.paused()) {
  36231. _this2.mainSegmentLoader_.load();
  36232. if (_this2.audioSegmentLoader_) {
  36233. _this2.audioSegmentLoader_.load();
  36234. }
  36235. }
  36236. if (!updatedPlaylist.endList) {
  36237. var addSeekableRange = function addSeekableRange() {
  36238. var seekable$$1 = _this2.seekable();
  36239. if (seekable$$1.length !== 0) {
  36240. _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
  36241. }
  36242. };
  36243. if (_this2.duration() !== Infinity) {
  36244. var onDurationchange = function onDurationchange() {
  36245. if (_this2.duration() === Infinity) {
  36246. addSeekableRange();
  36247. } else {
  36248. _this2.tech_.one('durationchange', onDurationchange);
  36249. }
  36250. };
  36251. _this2.tech_.one('durationchange', onDurationchange);
  36252. } else {
  36253. addSeekableRange();
  36254. }
  36255. }
  36256. });
  36257. this.masterPlaylistLoader_.on('error', function () {
  36258. _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
  36259. });
  36260. this.masterPlaylistLoader_.on('mediachanging', function () {
  36261. _this2.mainSegmentLoader_.abort();
  36262. _this2.mainSegmentLoader_.pause();
  36263. });
  36264. this.masterPlaylistLoader_.on('mediachange', function () {
  36265. var media = _this2.masterPlaylistLoader_.media();
  36266. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  36267. // timeout the request.
  36268. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  36269. _this2.requestOptions_.timeout = 0;
  36270. } else {
  36271. _this2.requestOptions_.timeout = requestTimeout;
  36272. } // TODO: Create a new event on the PlaylistLoader that signals
  36273. // that the segments have changed in some way and use that to
  36274. // update the SegmentLoader instead of doing it twice here and
  36275. // on `loadedplaylist`
  36276. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  36277. _this2.mainSegmentLoader_.load();
  36278. _this2.tech_.trigger({
  36279. type: 'mediachange',
  36280. bubbles: true
  36281. });
  36282. });
  36283. this.masterPlaylistLoader_.on('playlistunchanged', function () {
  36284. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  36285. var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
  36286. if (playlistOutdated) {
  36287. // Playlist has stopped updating and we're stuck at its end. Try to
  36288. // blacklist it and switch to another playlist in the hope that that
  36289. // one is updating (and give the player a chance to re-adjust to the
  36290. // safe live point).
  36291. _this2.blacklistCurrentPlaylist({
  36292. message: 'Playlist no longer updating.'
  36293. }); // useful for monitoring QoS
  36294. _this2.tech_.trigger('playliststuck');
  36295. }
  36296. });
  36297. this.masterPlaylistLoader_.on('renditiondisabled', function () {
  36298. _this2.tech_.trigger({
  36299. type: 'usage',
  36300. name: 'hls-rendition-disabled'
  36301. });
  36302. });
  36303. this.masterPlaylistLoader_.on('renditionenabled', function () {
  36304. _this2.tech_.trigger({
  36305. type: 'usage',
  36306. name: 'hls-rendition-enabled'
  36307. });
  36308. });
  36309. }
  36310. /**
  36311. * A helper function for triggerring presence usage events once per source
  36312. *
  36313. * @private
  36314. */
  36315. }, {
  36316. key: 'triggerPresenceUsage_',
  36317. value: function triggerPresenceUsage_(master, media) {
  36318. var mediaGroups = master.mediaGroups || {};
  36319. var defaultDemuxed = true;
  36320. var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
  36321. for (var mediaGroup in mediaGroups.AUDIO) {
  36322. for (var label in mediaGroups.AUDIO[mediaGroup]) {
  36323. var properties = mediaGroups.AUDIO[mediaGroup][label];
  36324. if (!properties.uri) {
  36325. defaultDemuxed = false;
  36326. }
  36327. }
  36328. }
  36329. if (defaultDemuxed) {
  36330. this.tech_.trigger({
  36331. type: 'usage',
  36332. name: 'hls-demuxed'
  36333. });
  36334. }
  36335. if (Object.keys(mediaGroups.SUBTITLES).length) {
  36336. this.tech_.trigger({
  36337. type: 'usage',
  36338. name: 'hls-webvtt'
  36339. });
  36340. }
  36341. if (Hls.Playlist.isAes(media)) {
  36342. this.tech_.trigger({
  36343. type: 'usage',
  36344. name: 'hls-aes'
  36345. });
  36346. }
  36347. if (Hls.Playlist.isFmp4(media)) {
  36348. this.tech_.trigger({
  36349. type: 'usage',
  36350. name: 'hls-fmp4'
  36351. });
  36352. }
  36353. if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
  36354. this.tech_.trigger({
  36355. type: 'usage',
  36356. name: 'hls-alternate-audio'
  36357. });
  36358. }
  36359. if (this.useCueTags_) {
  36360. this.tech_.trigger({
  36361. type: 'usage',
  36362. name: 'hls-playlist-cue-tags'
  36363. });
  36364. }
  36365. }
  36366. /**
  36367. * Register event handlers on the segment loaders. A helper function
  36368. * for construction time.
  36369. *
  36370. * @private
  36371. */
  36372. }, {
  36373. key: 'setupSegmentLoaderListeners_',
  36374. value: function setupSegmentLoaderListeners_() {
  36375. var _this3 = this;
  36376. this.mainSegmentLoader_.on('bandwidthupdate', function () {
  36377. var nextPlaylist = _this3.selectPlaylist();
  36378. var currentPlaylist = _this3.masterPlaylistLoader_.media();
  36379. var buffered = _this3.tech_.buffered();
  36380. var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
  36381. var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account.
  36382. // This is because in LIVE, the player plays 3 segments from the end of the
  36383. // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
  36384. // in those segments, a viewer will never experience a rendition upswitch.
  36385. if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD
  36386. // duration is below the max potential low water line
  36387. _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but
  36388. nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of
  36389. // buffer while loading a higher rendition.
  36390. forwardBuffer >= bufferLowWaterLine) {
  36391. _this3.masterPlaylistLoader_.media(nextPlaylist);
  36392. }
  36393. _this3.tech_.trigger('bandwidthupdate');
  36394. });
  36395. this.mainSegmentLoader_.on('progress', function () {
  36396. _this3.trigger('progress');
  36397. });
  36398. this.mainSegmentLoader_.on('error', function () {
  36399. _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
  36400. });
  36401. this.mainSegmentLoader_.on('syncinfoupdate', function () {
  36402. _this3.onSyncInfoUpdate_();
  36403. });
  36404. this.mainSegmentLoader_.on('timestampoffset', function () {
  36405. _this3.tech_.trigger({
  36406. type: 'usage',
  36407. name: 'hls-timestamp-offset'
  36408. });
  36409. });
  36410. this.audioSegmentLoader_.on('syncinfoupdate', function () {
  36411. _this3.onSyncInfoUpdate_();
  36412. });
  36413. this.mainSegmentLoader_.on('ended', function () {
  36414. _this3.onEndOfStream();
  36415. });
  36416. this.mainSegmentLoader_.on('earlyabort', function () {
  36417. _this3.blacklistCurrentPlaylist({
  36418. message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
  36419. }, ABORT_EARLY_BLACKLIST_SECONDS);
  36420. });
  36421. this.mainSegmentLoader_.on('reseteverything', function () {
  36422. // If playing an MTS stream, a videojs.MediaSource is listening for
  36423. // hls-reset to reset caption parsing state in the transmuxer
  36424. _this3.tech_.trigger('hls-reset');
  36425. });
  36426. this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
  36427. // If playing an MTS stream in html, a videojs.MediaSource is listening for
  36428. // hls-segment-time-mapping update its internal mapping of stream to display time
  36429. _this3.tech_.trigger({
  36430. type: 'hls-segment-time-mapping',
  36431. mapping: event.mapping
  36432. });
  36433. });
  36434. this.audioSegmentLoader_.on('ended', function () {
  36435. _this3.onEndOfStream();
  36436. });
  36437. }
  36438. }, {
  36439. key: 'mediaSecondsLoaded_',
  36440. value: function mediaSecondsLoaded_() {
  36441. return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
  36442. }
  36443. /**
  36444. * Call load on our SegmentLoaders
  36445. */
  36446. }, {
  36447. key: 'load',
  36448. value: function load() {
  36449. this.mainSegmentLoader_.load();
  36450. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36451. this.audioSegmentLoader_.load();
  36452. }
  36453. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  36454. this.subtitleSegmentLoader_.load();
  36455. }
  36456. }
  36457. /**
  36458. * Re-tune playback quality level for the current player
  36459. * conditions without performing destructive actions, like
  36460. * removing already buffered content
  36461. *
  36462. * @private
  36463. */
  36464. }, {
  36465. key: 'smoothQualityChange_',
  36466. value: function smoothQualityChange_() {
  36467. var media = this.selectPlaylist();
  36468. if (media !== this.masterPlaylistLoader_.media()) {
  36469. this.masterPlaylistLoader_.media(media);
  36470. this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes
  36471. }
  36472. }
  36473. /**
  36474. * Re-tune playback quality level for the current player
  36475. * conditions. This method will perform destructive actions like removing
  36476. * already buffered content in order to readjust the currently active
  36477. * playlist quickly. This is good for manual quality changes
  36478. *
  36479. * @private
  36480. */
  36481. }, {
  36482. key: 'fastQualityChange_',
  36483. value: function fastQualityChange_() {
  36484. var _this4 = this;
  36485. var media = this.selectPlaylist();
  36486. if (media === this.masterPlaylistLoader_.media()) {
  36487. return;
  36488. }
  36489. this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give
  36490. // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
  36491. // ahead is roughly the minimum that will accomplish this across a variety of content
  36492. // in IE and Edge, but seeking in place is sufficient on all other browsers)
  36493. // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
  36494. // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
  36495. this.mainSegmentLoader_.resetEverything(function () {
  36496. // Since this is not a typical seek, we avoid the seekTo method which can cause segments
  36497. // from the previously enabled rendition to load before the new playlist has finished loading
  36498. if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) {
  36499. _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04);
  36500. } else {
  36501. _this4.tech_.setCurrentTime(_this4.tech_.currentTime());
  36502. }
  36503. }); // don't need to reset audio as it is reset when media changes
  36504. }
  36505. /**
  36506. * Begin playback.
  36507. */
  36508. }, {
  36509. key: 'play',
  36510. value: function play() {
  36511. if (this.setupFirstPlay()) {
  36512. return;
  36513. }
  36514. if (this.tech_.ended()) {
  36515. this.seekTo_(0);
  36516. }
  36517. if (this.hasPlayed_()) {
  36518. this.load();
  36519. }
  36520. var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
  36521. // seek forward to the live point
  36522. if (this.tech_.duration() === Infinity) {
  36523. if (this.tech_.currentTime() < seekable$$1.start(0)) {
  36524. return this.seekTo_(seekable$$1.end(seekable$$1.length - 1));
  36525. }
  36526. }
  36527. }
  36528. /**
  36529. * Seek to the latest media position if this is a live video and the
  36530. * player and video are loaded and initialized.
  36531. */
  36532. }, {
  36533. key: 'setupFirstPlay',
  36534. value: function setupFirstPlay() {
  36535. var _this5 = this;
  36536. var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
  36537. // If 1) there is no active media
  36538. // 2) the player is paused
  36539. // 3) the first play has already been setup
  36540. // then exit early
  36541. if (!media || this.tech_.paused() || this.hasPlayed_()) {
  36542. return false;
  36543. } // when the video is a live stream
  36544. if (!media.endList) {
  36545. var seekable$$1 = this.seekable();
  36546. if (!seekable$$1.length) {
  36547. // without a seekable range, the player cannot seek to begin buffering at the live
  36548. // point
  36549. return false;
  36550. }
  36551. if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
  36552. // IE11 throws an InvalidStateError if you try to set currentTime while the
  36553. // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
  36554. this.tech_.one('loadedmetadata', function () {
  36555. _this5.trigger('firstplay');
  36556. _this5.seekTo_(seekable$$1.end(0));
  36557. _this5.hasPlayed_ = function () {
  36558. return true;
  36559. };
  36560. });
  36561. return false;
  36562. } // trigger firstplay to inform the source handler to ignore the next seek event
  36563. this.trigger('firstplay'); // seek to the live point
  36564. this.seekTo_(seekable$$1.end(0));
  36565. }
  36566. this.hasPlayed_ = function () {
  36567. return true;
  36568. }; // we can begin loading now that everything is ready
  36569. this.load();
  36570. return true;
  36571. }
  36572. /**
  36573. * handle the sourceopen event on the MediaSource
  36574. *
  36575. * @private
  36576. */
  36577. }, {
  36578. key: 'handleSourceOpen_',
  36579. value: function handleSourceOpen_() {
  36580. // Only attempt to create the source buffer if none already exist.
  36581. // handleSourceOpen is also called when we are "re-opening" a source buffer
  36582. // after `endOfStream` has been called (in response to a seek for instance)
  36583. try {
  36584. this.setupSourceBuffers_();
  36585. } catch (e) {
  36586. videojs$1.log.warn('Failed to create Source Buffers', e);
  36587. return this.mediaSource.endOfStream('decode');
  36588. } // if autoplay is enabled, begin playback. This is duplicative of
  36589. // code in video.js but is required because play() must be invoked
  36590. // *after* the media source has opened.
  36591. if (this.tech_.autoplay()) {
  36592. var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
  36593. // on browsers which return a promise
  36594. if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
  36595. playPromise.then(null, function (e) {});
  36596. }
  36597. }
  36598. this.trigger('sourceopen');
  36599. }
  36600. /**
  36601. * Calls endOfStream on the media source when all active stream types have called
  36602. * endOfStream
  36603. *
  36604. * @param {string} streamType
  36605. * Stream type of the segment loader that called endOfStream
  36606. * @private
  36607. */
  36608. }, {
  36609. key: 'onEndOfStream',
  36610. value: function onEndOfStream() {
  36611. var isEndOfStream = this.mainSegmentLoader_.ended_;
  36612. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36613. // if the audio playlist loader exists, then alternate audio is active
  36614. if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
  36615. // if we do not know if the main segment loader contains video yet or if we
  36616. // definitively know the main segment loader contains video, then we need to wait
  36617. // for both main and audio segment loaders to call endOfStream
  36618. isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
  36619. } else {
  36620. // otherwise just rely on the audio loader
  36621. isEndOfStream = this.audioSegmentLoader_.ended_;
  36622. }
  36623. }
  36624. if (!isEndOfStream) {
  36625. return;
  36626. }
  36627. this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception,
  36628. // even when the media source is in a valid state.
  36629. try {
  36630. this.mediaSource.endOfStream();
  36631. } catch (e) {
  36632. videojs$1.log.warn('Failed to call media source endOfStream', e);
  36633. }
  36634. }
  36635. /**
  36636. * Check if a playlist has stopped being updated
  36637. * @param {Object} playlist the media playlist object
  36638. * @return {boolean} whether the playlist has stopped being updated or not
  36639. */
  36640. }, {
  36641. key: 'stuckAtPlaylistEnd_',
  36642. value: function stuckAtPlaylistEnd_(playlist) {
  36643. var seekable$$1 = this.seekable();
  36644. if (!seekable$$1.length) {
  36645. // playlist doesn't have enough information to determine whether we are stuck
  36646. return false;
  36647. }
  36648. var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
  36649. if (expired === null) {
  36650. return false;
  36651. } // does not use the safe live end to calculate playlist end, since we
  36652. // don't want to say we are stuck while there is still content
  36653. var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
  36654. var currentTime = this.tech_.currentTime();
  36655. var buffered = this.tech_.buffered();
  36656. if (!buffered.length) {
  36657. // return true if the playhead reached the absolute end of the playlist
  36658. return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
  36659. }
  36660. var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
  36661. // end of playlist
  36662. return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
  36663. }
  36664. /**
  36665. * Blacklists a playlist when an error occurs for a set amount of time
  36666. * making it unavailable for selection by the rendition selection algorithm
  36667. * and then forces a new playlist (rendition) selection.
  36668. *
  36669. * @param {Object=} error an optional error that may include the playlist
  36670. * to blacklist
  36671. * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
  36672. * playlist
  36673. */
  36674. }, {
  36675. key: 'blacklistCurrentPlaylist',
  36676. value: function blacklistCurrentPlaylist() {
  36677. var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  36678. var blacklistDuration = arguments[1];
  36679. var currentPlaylist = void 0;
  36680. var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain
  36681. // the playlist we were trying to load (but failed) and that should be
  36682. // blacklisted instead of the currently selected playlist which is likely
  36683. // out-of-date in this scenario
  36684. currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
  36685. blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
  36686. // trying to load the master OR while we were disposing of the tech
  36687. if (!currentPlaylist) {
  36688. this.error = error;
  36689. try {
  36690. return this.mediaSource.endOfStream('network');
  36691. } catch (e) {
  36692. return this.trigger('error');
  36693. }
  36694. }
  36695. var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
  36696. if (isFinalRendition) {
  36697. // Never blacklisting this playlist because it's final rendition
  36698. videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.');
  36699. this.tech_.trigger('retryplaylist');
  36700. return this.masterPlaylistLoader_.load(isFinalRendition);
  36701. } // Blacklist this playlist
  36702. currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
  36703. this.tech_.trigger('blacklistplaylist');
  36704. this.tech_.trigger({
  36705. type: 'usage',
  36706. name: 'hls-rendition-blacklisted'
  36707. }); // Select a new playlist
  36708. nextPlaylist = this.selectPlaylist();
  36709. videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
  36710. return this.masterPlaylistLoader_.media(nextPlaylist);
  36711. }
  36712. /**
  36713. * Pause all segment loaders
  36714. */
  36715. }, {
  36716. key: 'pauseLoading',
  36717. value: function pauseLoading() {
  36718. this.mainSegmentLoader_.pause();
  36719. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36720. this.audioSegmentLoader_.pause();
  36721. }
  36722. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  36723. this.subtitleSegmentLoader_.pause();
  36724. }
  36725. }
  36726. /**
  36727. * set the current time on all segment loaders
  36728. *
  36729. * @param {TimeRange} currentTime the current time to set
  36730. * @return {TimeRange} the current time
  36731. */
  36732. }, {
  36733. key: 'setCurrentTime',
  36734. value: function setCurrentTime(currentTime) {
  36735. var buffered = findRange(this.tech_.buffered(), currentTime);
  36736. if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
  36737. // return immediately if the metadata is not ready yet
  36738. return 0;
  36739. } // it's clearly an edge-case but don't thrown an error if asked to
  36740. // seek within an empty playlist
  36741. if (!this.masterPlaylistLoader_.media().segments) {
  36742. return 0;
  36743. } // In flash playback, the segment loaders should be reset on every seek, even
  36744. // in buffer seeks. If the seek location is already buffered, continue buffering as
  36745. // usual
  36746. // TODO: redo this comment
  36747. if (buffered && buffered.length) {
  36748. return currentTime;
  36749. } // cancel outstanding requests so we begin buffering at the new
  36750. // location
  36751. this.mainSegmentLoader_.resetEverything();
  36752. this.mainSegmentLoader_.abort();
  36753. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36754. this.audioSegmentLoader_.resetEverything();
  36755. this.audioSegmentLoader_.abort();
  36756. }
  36757. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  36758. this.subtitleSegmentLoader_.resetEverything();
  36759. this.subtitleSegmentLoader_.abort();
  36760. } // start segment loader loading in case they are paused
  36761. this.load();
  36762. }
  36763. /**
  36764. * get the current duration
  36765. *
  36766. * @return {TimeRange} the duration
  36767. */
  36768. }, {
  36769. key: 'duration',
  36770. value: function duration$$1() {
  36771. if (!this.masterPlaylistLoader_) {
  36772. return 0;
  36773. }
  36774. if (this.mediaSource) {
  36775. return this.mediaSource.duration;
  36776. }
  36777. return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  36778. }
  36779. /**
  36780. * check the seekable range
  36781. *
  36782. * @return {TimeRange} the seekable range
  36783. */
  36784. }, {
  36785. key: 'seekable',
  36786. value: function seekable$$1() {
  36787. return this.seekable_;
  36788. }
  36789. }, {
  36790. key: 'onSyncInfoUpdate_',
  36791. value: function onSyncInfoUpdate_() {
  36792. var mainSeekable = void 0;
  36793. var audioSeekable = void 0;
  36794. if (!this.masterPlaylistLoader_) {
  36795. return;
  36796. }
  36797. var media = this.masterPlaylistLoader_.media();
  36798. if (!media) {
  36799. return;
  36800. }
  36801. var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  36802. if (expired === null) {
  36803. // not enough information to update seekable
  36804. return;
  36805. }
  36806. mainSeekable = Hls.Playlist.seekable(media, expired);
  36807. if (mainSeekable.length === 0) {
  36808. return;
  36809. }
  36810. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36811. media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
  36812. expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  36813. if (expired === null) {
  36814. return;
  36815. }
  36816. audioSeekable = Hls.Playlist.seekable(media, expired);
  36817. if (audioSeekable.length === 0) {
  36818. return;
  36819. }
  36820. }
  36821. var oldEnd = void 0;
  36822. var oldStart = void 0;
  36823. if (this.seekable_ && this.seekable_.length) {
  36824. oldEnd = this.seekable_.end(0);
  36825. oldStart = this.seekable_.start(0);
  36826. }
  36827. if (!audioSeekable) {
  36828. // seekable has been calculated based on buffering video data so it
  36829. // can be returned directly
  36830. this.seekable_ = mainSeekable;
  36831. } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
  36832. // seekables are pretty far off, rely on main
  36833. this.seekable_ = mainSeekable;
  36834. } else {
  36835. 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)]]);
  36836. } // seekable is the same as last time
  36837. if (this.seekable_ && this.seekable_.length) {
  36838. if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
  36839. return;
  36840. }
  36841. }
  36842. this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
  36843. this.tech_.trigger('seekablechanged');
  36844. }
  36845. /**
  36846. * Update the player duration
  36847. */
  36848. }, {
  36849. key: 'updateDuration',
  36850. value: function updateDuration() {
  36851. var _this6 = this;
  36852. var oldDuration = this.mediaSource.duration;
  36853. var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  36854. var buffered = this.tech_.buffered();
  36855. var setDuration = function setDuration() {
  36856. // on firefox setting the duration may sometimes cause an exception
  36857. // even if the media source is open and source buffers are not
  36858. // updating, something about the media source being in an invalid state.
  36859. _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration);
  36860. try {
  36861. _this6.mediaSource.duration = newDuration;
  36862. } catch (e) {
  36863. videojs$1.log.warn('Failed to set media source duration', e);
  36864. }
  36865. _this6.tech_.trigger('durationchange');
  36866. _this6.mediaSource.removeEventListener('sourceopen', setDuration);
  36867. };
  36868. if (buffered.length > 0) {
  36869. newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
  36870. } // if the duration has changed, invalidate the cached value
  36871. if (oldDuration !== newDuration) {
  36872. // update the duration
  36873. if (this.mediaSource.readyState !== 'open') {
  36874. this.mediaSource.addEventListener('sourceopen', setDuration);
  36875. } else {
  36876. setDuration();
  36877. }
  36878. }
  36879. }
  36880. /**
  36881. * dispose of the MasterPlaylistController and everything
  36882. * that it controls
  36883. */
  36884. }, {
  36885. key: 'dispose',
  36886. value: function dispose() {
  36887. var _this7 = this;
  36888. this.decrypter_.terminate();
  36889. this.masterPlaylistLoader_.dispose();
  36890. this.mainSegmentLoader_.dispose();
  36891. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  36892. var groups = _this7.mediaTypes_[type].groups;
  36893. for (var id in groups) {
  36894. groups[id].forEach(function (group) {
  36895. if (group.playlistLoader) {
  36896. group.playlistLoader.dispose();
  36897. }
  36898. });
  36899. }
  36900. });
  36901. this.audioSegmentLoader_.dispose();
  36902. this.subtitleSegmentLoader_.dispose();
  36903. }
  36904. /**
  36905. * return the master playlist object if we have one
  36906. *
  36907. * @return {Object} the master playlist object that we parsed
  36908. */
  36909. }, {
  36910. key: 'master',
  36911. value: function master() {
  36912. return this.masterPlaylistLoader_.master;
  36913. }
  36914. /**
  36915. * return the currently selected playlist
  36916. *
  36917. * @return {Object} the currently selected playlist object that we parsed
  36918. */
  36919. }, {
  36920. key: 'media',
  36921. value: function media() {
  36922. // playlist loader will not return media if it has not been fully loaded
  36923. return this.masterPlaylistLoader_.media() || this.initialMedia_;
  36924. }
  36925. /**
  36926. * setup our internal source buffers on our segment Loaders
  36927. *
  36928. * @private
  36929. */
  36930. }, {
  36931. key: 'setupSourceBuffers_',
  36932. value: function setupSourceBuffers_() {
  36933. var media = this.masterPlaylistLoader_.media();
  36934. var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is
  36935. // attached
  36936. if (!media || this.mediaSource.readyState !== 'open') {
  36937. return;
  36938. }
  36939. mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
  36940. if (mimeTypes.length < 1) {
  36941. this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
  36942. return this.mediaSource.endOfStream('decode');
  36943. }
  36944. this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist
  36945. // selection
  36946. this.excludeIncompatibleVariants_(media);
  36947. }
  36948. }, {
  36949. key: 'configureLoaderMimeTypes_',
  36950. value: function configureLoaderMimeTypes_(mimeTypes) {
  36951. // If the content is demuxed, we can't start appending segments to a source buffer
  36952. // until both source buffers are set up, or else the browser may not let us add the
  36953. // second source buffer (it will assume we are playing either audio only or video
  36954. // only).
  36955. var sourceBufferEmitter = // If there is more than one mime type
  36956. mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio
  36957. mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio
  36958. // only with alternate audio)
  36959. mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer
  36960. new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only,
  36961. // video only, or muxed content.
  36962. null;
  36963. this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
  36964. if (mimeTypes[1]) {
  36965. this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
  36966. }
  36967. }
  36968. /**
  36969. * Blacklists playlists with codecs that are unsupported by the browser.
  36970. */
  36971. }, {
  36972. key: 'excludeUnsupportedVariants_',
  36973. value: function excludeUnsupportedVariants_() {
  36974. this.master().playlists.forEach(function (variant) {
  36975. if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
  36976. variant.excludeUntil = Infinity;
  36977. }
  36978. });
  36979. }
  36980. /**
  36981. * Blacklist playlists that are known to be codec or
  36982. * stream-incompatible with the SourceBuffer configuration. For
  36983. * instance, Media Source Extensions would cause the video element to
  36984. * stall waiting for video data if you switched from a variant with
  36985. * video and audio to an audio-only one.
  36986. *
  36987. * @param {Object} media a media playlist compatible with the current
  36988. * set of SourceBuffers. Variants in the current master playlist that
  36989. * do not appear to have compatible codec or stream configurations
  36990. * will be excluded from the default playlist selection algorithm
  36991. * indefinitely.
  36992. * @private
  36993. */
  36994. }, {
  36995. key: 'excludeIncompatibleVariants_',
  36996. value: function excludeIncompatibleVariants_(media) {
  36997. var codecCount = 2;
  36998. var videoCodec = null;
  36999. var codecs = void 0;
  37000. if (media.attributes.CODECS) {
  37001. codecs = parseCodecs(media.attributes.CODECS);
  37002. videoCodec = codecs.videoCodec;
  37003. codecCount = codecs.codecCount;
  37004. }
  37005. this.master().playlists.forEach(function (variant) {
  37006. var variantCodecs = {
  37007. codecCount: 2,
  37008. videoCodec: null
  37009. };
  37010. if (variant.attributes.CODECS) {
  37011. variantCodecs = parseCodecs(variant.attributes.CODECS);
  37012. } // if the streams differ in the presence or absence of audio or
  37013. // video, they are incompatible
  37014. if (variantCodecs.codecCount !== codecCount) {
  37015. variant.excludeUntil = Infinity;
  37016. } // if h.264 is specified on the current playlist, some flavor of
  37017. // it must be specified on all compatible variants
  37018. if (variantCodecs.videoCodec !== videoCodec) {
  37019. variant.excludeUntil = Infinity;
  37020. }
  37021. });
  37022. }
  37023. }, {
  37024. key: 'updateAdCues_',
  37025. value: function updateAdCues_(media) {
  37026. var offset = 0;
  37027. var seekable$$1 = this.seekable();
  37028. if (seekable$$1.length) {
  37029. offset = seekable$$1.start(0);
  37030. }
  37031. updateAdCues(media, this.cueTagsTrack_, offset);
  37032. }
  37033. /**
  37034. * Calculates the desired forward buffer length based on current time
  37035. *
  37036. * @return {Number} Desired forward buffer length in seconds
  37037. */
  37038. }, {
  37039. key: 'goalBufferLength',
  37040. value: function goalBufferLength() {
  37041. var currentTime = this.tech_.currentTime();
  37042. var initial = Config.GOAL_BUFFER_LENGTH;
  37043. var rate = Config.GOAL_BUFFER_LENGTH_RATE;
  37044. var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
  37045. return Math.min(initial + currentTime * rate, max);
  37046. }
  37047. /**
  37048. * Calculates the desired buffer low water line based on current time
  37049. *
  37050. * @return {Number} Desired buffer low water line in seconds
  37051. */
  37052. }, {
  37053. key: 'bufferLowWaterLine',
  37054. value: function bufferLowWaterLine() {
  37055. var currentTime = this.tech_.currentTime();
  37056. var initial = Config.BUFFER_LOW_WATER_LINE;
  37057. var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
  37058. var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
  37059. return Math.min(initial + currentTime * rate, max);
  37060. }
  37061. }]);
  37062. return MasterPlaylistController;
  37063. }(videojs$1.EventTarget);
  37064. /**
  37065. * Returns a function that acts as the Enable/disable playlist function.
  37066. *
  37067. * @param {PlaylistLoader} loader - The master playlist loader
  37068. * @param {String} playlistUri - uri of the playlist
  37069. * @param {Function} changePlaylistFn - A function to be called after a
  37070. * playlist's enabled-state has been changed. Will NOT be called if a
  37071. * playlist's enabled-state is unchanged
  37072. * @param {Boolean=} enable - Value to set the playlist enabled-state to
  37073. * or if undefined returns the current enabled-state for the playlist
  37074. * @return {Function} Function for setting/getting enabled
  37075. */
  37076. var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
  37077. return function (enable) {
  37078. var playlist = loader.master.playlists[playlistUri];
  37079. var incompatible = isIncompatible(playlist);
  37080. var currentlyEnabled = isEnabled(playlist);
  37081. if (typeof enable === 'undefined') {
  37082. return currentlyEnabled;
  37083. }
  37084. if (enable) {
  37085. delete playlist.disabled;
  37086. } else {
  37087. playlist.disabled = true;
  37088. }
  37089. if (enable !== currentlyEnabled && !incompatible) {
  37090. // Ensure the outside world knows about our changes
  37091. changePlaylistFn();
  37092. if (enable) {
  37093. loader.trigger('renditionenabled');
  37094. } else {
  37095. loader.trigger('renditiondisabled');
  37096. }
  37097. }
  37098. return enable;
  37099. };
  37100. };
  37101. /**
  37102. * The representation object encapsulates the publicly visible information
  37103. * in a media playlist along with a setter/getter-type function (enabled)
  37104. * for changing the enabled-state of a particular playlist entry
  37105. *
  37106. * @class Representation
  37107. */
  37108. var Representation = function Representation(hlsHandler, playlist, id) {
  37109. classCallCheck(this, Representation);
  37110. var mpc = hlsHandler.masterPlaylistController_,
  37111. smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
  37112. var changeType = smoothQualityChange ? 'smooth' : 'fast';
  37113. var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional
  37114. if (playlist.attributes.RESOLUTION) {
  37115. var resolution = playlist.attributes.RESOLUTION;
  37116. this.width = resolution.width;
  37117. this.height = resolution.height;
  37118. }
  37119. this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist
  37120. // within the master playlist
  37121. this.id = id; // Partially-apply the enableFunction to create a playlist-
  37122. // specific variant
  37123. this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction);
  37124. };
  37125. /**
  37126. * A mixin function that adds the `representations` api to an instance
  37127. * of the HlsHandler class
  37128. * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
  37129. * representation API into
  37130. */
  37131. var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
  37132. var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance
  37133. hlsHandler.representations = function () {
  37134. return playlists.master.playlists.filter(function (media) {
  37135. return !isIncompatible(media);
  37136. }).map(function (e, i) {
  37137. return new Representation(hlsHandler, e, e.uri);
  37138. });
  37139. };
  37140. };
  37141. /**
  37142. * @file playback-watcher.js
  37143. *
  37144. * Playback starts, and now my watch begins. It shall not end until my death. I shall
  37145. * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
  37146. * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
  37147. * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
  37148. * my life and honor to the Playback Watch, for this Player and all the Players to come.
  37149. */
  37150. // Set of events that reset the playback-watcher time check logic and clear the timeout
  37151. var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
  37152. /**
  37153. * @class PlaybackWatcher
  37154. */
  37155. var PlaybackWatcher = function () {
  37156. /**
  37157. * Represents an PlaybackWatcher object.
  37158. * @constructor
  37159. * @param {object} options an object that includes the tech and settings
  37160. */
  37161. function PlaybackWatcher(options) {
  37162. var _this = this;
  37163. classCallCheck(this, PlaybackWatcher);
  37164. this.tech_ = options.tech;
  37165. this.seekable = options.seekable;
  37166. this.seekTo = options.seekTo;
  37167. this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
  37168. this.media = options.media;
  37169. this.consecutiveUpdates = 0;
  37170. this.lastRecordedTime = null;
  37171. this.timer_ = null;
  37172. this.checkCurrentTimeTimeout_ = null;
  37173. this.logger_ = logger('PlaybackWatcher');
  37174. this.logger_('initialize');
  37175. var canPlayHandler = function canPlayHandler() {
  37176. return _this.monitorCurrentTime_();
  37177. };
  37178. var waitingHandler = function waitingHandler() {
  37179. return _this.techWaiting_();
  37180. };
  37181. var cancelTimerHandler = function cancelTimerHandler() {
  37182. return _this.cancelTimer_();
  37183. };
  37184. var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
  37185. return _this.fixesBadSeeks_();
  37186. };
  37187. this.tech_.on('seekablechanged', fixesBadSeeksHandler);
  37188. this.tech_.on('waiting', waitingHandler);
  37189. this.tech_.on(timerCancelEvents, cancelTimerHandler);
  37190. this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events
  37191. this.dispose = function () {
  37192. _this.logger_('dispose');
  37193. _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
  37194. _this.tech_.off('waiting', waitingHandler);
  37195. _this.tech_.off(timerCancelEvents, cancelTimerHandler);
  37196. _this.tech_.off('canplay', canPlayHandler);
  37197. if (_this.checkCurrentTimeTimeout_) {
  37198. window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
  37199. }
  37200. _this.cancelTimer_();
  37201. };
  37202. }
  37203. /**
  37204. * Periodically check current time to see if playback stopped
  37205. *
  37206. * @private
  37207. */
  37208. createClass(PlaybackWatcher, [{
  37209. key: 'monitorCurrentTime_',
  37210. value: function monitorCurrentTime_() {
  37211. this.checkCurrentTime_();
  37212. if (this.checkCurrentTimeTimeout_) {
  37213. window$1.clearTimeout(this.checkCurrentTimeTimeout_);
  37214. } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  37215. this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
  37216. }
  37217. /**
  37218. * The purpose of this function is to emulate the "waiting" event on
  37219. * browsers that do not emit it when they are waiting for more
  37220. * data to continue playback
  37221. *
  37222. * @private
  37223. */
  37224. }, {
  37225. key: 'checkCurrentTime_',
  37226. value: function checkCurrentTime_() {
  37227. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  37228. this.consecutiveUpdates = 0;
  37229. this.lastRecordedTime = this.tech_.currentTime();
  37230. return;
  37231. }
  37232. if (this.tech_.paused() || this.tech_.seeking()) {
  37233. return;
  37234. }
  37235. var currentTime = this.tech_.currentTime();
  37236. var buffered = this.tech_.buffered();
  37237. if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
  37238. // If current time is at the end of the final buffered region, then any playback
  37239. // stall is most likely caused by buffering in a low bandwidth environment. The tech
  37240. // should fire a `waiting` event in this scenario, but due to browser and tech
  37241. // inconsistencies. Calling `techWaiting_` here allows us to simulate
  37242. // responding to a native `waiting` event when the tech fails to emit one.
  37243. return this.techWaiting_();
  37244. }
  37245. if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
  37246. this.consecutiveUpdates++;
  37247. this.waiting_();
  37248. } else if (currentTime === this.lastRecordedTime) {
  37249. this.consecutiveUpdates++;
  37250. } else {
  37251. this.consecutiveUpdates = 0;
  37252. this.lastRecordedTime = currentTime;
  37253. }
  37254. }
  37255. /**
  37256. * Cancels any pending timers and resets the 'timeupdate' mechanism
  37257. * designed to detect that we are stalled
  37258. *
  37259. * @private
  37260. */
  37261. }, {
  37262. key: 'cancelTimer_',
  37263. value: function cancelTimer_() {
  37264. this.consecutiveUpdates = 0;
  37265. if (this.timer_) {
  37266. this.logger_('cancelTimer_');
  37267. clearTimeout(this.timer_);
  37268. }
  37269. this.timer_ = null;
  37270. }
  37271. /**
  37272. * Fixes situations where there's a bad seek
  37273. *
  37274. * @return {Boolean} whether an action was taken to fix the seek
  37275. * @private
  37276. */
  37277. }, {
  37278. key: 'fixesBadSeeks_',
  37279. value: function fixesBadSeeks_() {
  37280. var seeking = this.tech_.seeking();
  37281. if (!seeking) {
  37282. return false;
  37283. }
  37284. var seekable = this.seekable();
  37285. var currentTime = this.tech_.currentTime();
  37286. var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
  37287. var seekTo = void 0;
  37288. if (isAfterSeekableRange) {
  37289. var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
  37290. seekTo = seekableEnd;
  37291. }
  37292. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  37293. var seekableStart = seekable.start(0); // sync to the beginning of the live window
  37294. // provide a buffer of .1 seconds to handle rounding/imprecise numbers
  37295. seekTo = seekableStart + SAFE_TIME_DELTA;
  37296. }
  37297. if (typeof seekTo !== 'undefined') {
  37298. this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
  37299. this.seekTo(seekTo);
  37300. return true;
  37301. }
  37302. return false;
  37303. }
  37304. /**
  37305. * Handler for situations when we determine the player is waiting.
  37306. *
  37307. * @private
  37308. */
  37309. }, {
  37310. key: 'waiting_',
  37311. value: function waiting_() {
  37312. if (this.techWaiting_()) {
  37313. return;
  37314. } // All tech waiting checks failed. Use last resort correction
  37315. var currentTime = this.tech_.currentTime();
  37316. var buffered = this.tech_.buffered();
  37317. var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
  37318. // region with no indication that anything is amiss (seen in Firefox). Seeking to
  37319. // currentTime is usually enough to kickstart the player. This checks that the player
  37320. // is currently within a buffered region before attempting a corrective seek.
  37321. // Chrome does not appear to continue `timeupdate` events after a `waiting` event
  37322. // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
  37323. // make sure there is ~3 seconds of forward buffer before taking any corrective action
  37324. // to avoid triggering an `unknownwaiting` event when the network is slow.
  37325. if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
  37326. this.cancelTimer_();
  37327. this.seekTo(currentTime);
  37328. 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
  37329. this.tech_.trigger({
  37330. type: 'usage',
  37331. name: 'hls-unknown-waiting'
  37332. });
  37333. return;
  37334. }
  37335. }
  37336. /**
  37337. * Handler for situations when the tech fires a `waiting` event
  37338. *
  37339. * @return {Boolean}
  37340. * True if an action (or none) was needed to correct the waiting. False if no
  37341. * checks passed
  37342. * @private
  37343. */
  37344. }, {
  37345. key: 'techWaiting_',
  37346. value: function techWaiting_() {
  37347. var seekable = this.seekable();
  37348. var currentTime = this.tech_.currentTime();
  37349. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  37350. // Tech is seeking or bad seek fixed, no action needed
  37351. return true;
  37352. }
  37353. if (this.tech_.seeking() || this.timer_ !== null) {
  37354. // Tech is seeking or already waiting on another action, no action needed
  37355. return true;
  37356. }
  37357. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  37358. var livePoint = seekable.end(seekable.length - 1);
  37359. this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
  37360. this.cancelTimer_();
  37361. this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS
  37362. this.tech_.trigger({
  37363. type: 'usage',
  37364. name: 'hls-live-resync'
  37365. });
  37366. return true;
  37367. }
  37368. var buffered = this.tech_.buffered();
  37369. var nextRange = findNextRange(buffered, currentTime);
  37370. if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
  37371. // Even though the video underflowed and was stuck in a gap, the audio overplayed
  37372. // the gap, leading currentTime into a buffered range. Seeking to currentTime
  37373. // allows the video to catch up to the audio position without losing any audio
  37374. // (only suffering ~3 seconds of frozen video and a pause in audio playback).
  37375. this.cancelTimer_();
  37376. this.seekTo(currentTime); // video underflow may be useful for monitoring QoS
  37377. this.tech_.trigger({
  37378. type: 'usage',
  37379. name: 'hls-video-underflow'
  37380. });
  37381. return true;
  37382. } // check for gap
  37383. if (nextRange.length > 0) {
  37384. var difference = nextRange.start(0) - currentTime;
  37385. this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
  37386. this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
  37387. return true;
  37388. } // All checks failed. Returning false to indicate failure to correct waiting
  37389. return false;
  37390. }
  37391. }, {
  37392. key: 'afterSeekableWindow_',
  37393. value: function afterSeekableWindow_(seekable, currentTime, playlist) {
  37394. var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  37395. if (!seekable.length) {
  37396. // we can't make a solid case if there's no seekable, default to false
  37397. return false;
  37398. }
  37399. var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
  37400. var isLive = !playlist.endList;
  37401. if (isLive && allowSeeksWithinUnsafeLiveWindow) {
  37402. allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
  37403. }
  37404. if (currentTime > allowedEnd) {
  37405. return true;
  37406. }
  37407. return false;
  37408. }
  37409. }, {
  37410. key: 'beforeSeekableWindow_',
  37411. value: function beforeSeekableWindow_(seekable, currentTime) {
  37412. if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
  37413. seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
  37414. return true;
  37415. }
  37416. return false;
  37417. }
  37418. }, {
  37419. key: 'videoUnderflow_',
  37420. value: function videoUnderflow_(nextRange, buffered, currentTime) {
  37421. if (nextRange.length === 0) {
  37422. // Even if there is no available next range, there is still a possibility we are
  37423. // stuck in a gap due to video underflow.
  37424. var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
  37425. if (gap) {
  37426. this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
  37427. return true;
  37428. }
  37429. }
  37430. return false;
  37431. }
  37432. /**
  37433. * Timer callback. If playback still has not proceeded, then we seek
  37434. * to the start of the next buffered region.
  37435. *
  37436. * @private
  37437. */
  37438. }, {
  37439. key: 'skipTheGap_',
  37440. value: function skipTheGap_(scheduledCurrentTime) {
  37441. var buffered = this.tech_.buffered();
  37442. var currentTime = this.tech_.currentTime();
  37443. var nextRange = findNextRange(buffered, currentTime);
  37444. this.cancelTimer_();
  37445. if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
  37446. return;
  37447. }
  37448. this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
  37449. this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR);
  37450. this.tech_.trigger({
  37451. type: 'usage',
  37452. name: 'hls-gap-skip'
  37453. });
  37454. }
  37455. }, {
  37456. key: 'gapFromVideoUnderflow_',
  37457. value: function gapFromVideoUnderflow_(buffered, currentTime) {
  37458. // At least in Chrome, if there is a gap in the video buffer, the audio will continue
  37459. // playing for ~3 seconds after the video gap starts. This is done to account for
  37460. // video buffer underflow/underrun (note that this is not done when there is audio
  37461. // buffer underflow/underrun -- in that case the video will stop as soon as it
  37462. // encounters the gap, as audio stalls are more noticeable/jarring to a user than
  37463. // video stalls). The player's time will reflect the playthrough of audio, so the
  37464. // time will appear as if we are in a buffered region, even if we are stuck in a
  37465. // "gap."
  37466. //
  37467. // Example:
  37468. // video buffer: 0 => 10.1, 10.2 => 20
  37469. // audio buffer: 0 => 20
  37470. // overall buffer: 0 => 10.1, 10.2 => 20
  37471. // current time: 13
  37472. //
  37473. // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
  37474. // however, the audio continued playing until it reached ~3 seconds past the gap
  37475. // (13 seconds), at which point it stops as well. Since current time is past the
  37476. // gap, findNextRange will return no ranges.
  37477. //
  37478. // To check for this issue, we see if there is a gap that starts somewhere within
  37479. // a 3 second range (3 seconds +/- 1 second) back from our current time.
  37480. var gaps = findGaps(buffered);
  37481. for (var i = 0; i < gaps.length; i++) {
  37482. var start = gaps.start(i);
  37483. var end = gaps.end(i); // gap is starts no more than 4 seconds back
  37484. if (currentTime - start < 4 && currentTime - start > 2) {
  37485. return {
  37486. start: start,
  37487. end: end
  37488. };
  37489. }
  37490. }
  37491. return null;
  37492. }
  37493. }]);
  37494. return PlaybackWatcher;
  37495. }();
  37496. var defaultOptions = {
  37497. errorInterval: 30,
  37498. getSource: function getSource(next) {
  37499. var tech = this.tech({
  37500. IWillNotUseThisInPlugins: true
  37501. });
  37502. var sourceObj = tech.currentSource_;
  37503. return next(sourceObj);
  37504. }
  37505. };
  37506. /**
  37507. * Main entry point for the plugin
  37508. *
  37509. * @param {Player} player a reference to a videojs Player instance
  37510. * @param {Object} [options] an object with plugin options
  37511. * @private
  37512. */
  37513. var initPlugin = function initPlugin(player, options) {
  37514. var lastCalled = 0;
  37515. var seekTo = 0;
  37516. var localOptions = videojs$1.mergeOptions(defaultOptions, options);
  37517. player.ready(function () {
  37518. player.trigger({
  37519. type: 'usage',
  37520. name: 'hls-error-reload-initialized'
  37521. });
  37522. });
  37523. /**
  37524. * Player modifications to perform that must wait until `loadedmetadata`
  37525. * has been triggered
  37526. *
  37527. * @private
  37528. */
  37529. var loadedMetadataHandler = function loadedMetadataHandler() {
  37530. if (seekTo) {
  37531. player.currentTime(seekTo);
  37532. }
  37533. };
  37534. /**
  37535. * Set the source on the player element, play, and seek if necessary
  37536. *
  37537. * @param {Object} sourceObj An object specifying the source url and mime-type to play
  37538. * @private
  37539. */
  37540. var setSource = function setSource(sourceObj) {
  37541. if (sourceObj === null || sourceObj === undefined) {
  37542. return;
  37543. }
  37544. seekTo = player.duration() !== Infinity && player.currentTime() || 0;
  37545. player.one('loadedmetadata', loadedMetadataHandler);
  37546. player.src(sourceObj);
  37547. player.trigger({
  37548. type: 'usage',
  37549. name: 'hls-error-reload'
  37550. });
  37551. player.play();
  37552. };
  37553. /**
  37554. * Attempt to get a source from either the built-in getSource function
  37555. * or a custom function provided via the options
  37556. *
  37557. * @private
  37558. */
  37559. var errorHandler = function errorHandler() {
  37560. // Do not attempt to reload the source if a source-reload occurred before
  37561. // 'errorInterval' time has elapsed since the last source-reload
  37562. if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
  37563. player.trigger({
  37564. type: 'usage',
  37565. name: 'hls-error-reload-canceled'
  37566. });
  37567. return;
  37568. }
  37569. if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
  37570. videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
  37571. return;
  37572. }
  37573. lastCalled = Date.now();
  37574. return localOptions.getSource.call(player, setSource);
  37575. };
  37576. /**
  37577. * Unbind any event handlers that were bound by the plugin
  37578. *
  37579. * @private
  37580. */
  37581. var cleanupEvents = function cleanupEvents() {
  37582. player.off('loadedmetadata', loadedMetadataHandler);
  37583. player.off('error', errorHandler);
  37584. player.off('dispose', cleanupEvents);
  37585. };
  37586. /**
  37587. * Cleanup before re-initializing the plugin
  37588. *
  37589. * @param {Object} [newOptions] an object with plugin options
  37590. * @private
  37591. */
  37592. var reinitPlugin = function reinitPlugin(newOptions) {
  37593. cleanupEvents();
  37594. initPlugin(player, newOptions);
  37595. };
  37596. player.on('error', errorHandler);
  37597. player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
  37598. // initializing the plugin
  37599. player.reloadSourceOnError = reinitPlugin;
  37600. };
  37601. /**
  37602. * Reload the source when an error is detected as long as there
  37603. * wasn't an error previously within the last 30 seconds
  37604. *
  37605. * @param {Object} [options] an object with plugin options
  37606. */
  37607. var reloadSourceOnError = function reloadSourceOnError(options) {
  37608. initPlugin(this, options);
  37609. };
  37610. var version$1 = "1.10.1"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all
  37611. videojs$1.use('*', function (player) {
  37612. return {
  37613. setSource: function setSource(srcObj, next) {
  37614. // pass null as the first argument to indicate that the source is not rejected
  37615. next(null, srcObj);
  37616. },
  37617. // VHS needs to know when seeks happen. For external seeks (generated at the player
  37618. // level), this middleware will capture the action. For internal seeks (generated at
  37619. // the tech level), we use a wrapped function so that we can handle it on our own
  37620. // (specified elsewhere).
  37621. setCurrentTime: function setCurrentTime(time) {
  37622. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  37623. player.vhs.setCurrentTime(time);
  37624. }
  37625. return time;
  37626. },
  37627. // Sync VHS after play requests.
  37628. // This specifically handles replay where the order of actions is
  37629. // play, video element will seek to 0 (skipping the setCurrentTime middleware)
  37630. // then triggers a play event.
  37631. play: function play() {
  37632. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  37633. player.vhs.setCurrentTime(player.tech_.currentTime());
  37634. }
  37635. }
  37636. };
  37637. });
  37638. /**
  37639. * @file videojs-http-streaming.js
  37640. *
  37641. * The main file for the HLS project.
  37642. * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
  37643. */
  37644. var Hls$1 = {
  37645. PlaylistLoader: PlaylistLoader,
  37646. Playlist: Playlist,
  37647. Decrypter: aesDecrypter.Decrypter,
  37648. AsyncStream: aesDecrypter.AsyncStream,
  37649. decrypt: aesDecrypter.decrypt,
  37650. utils: utils,
  37651. STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
  37652. INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
  37653. comparePlaylistBandwidth: comparePlaylistBandwidth,
  37654. comparePlaylistResolution: comparePlaylistResolution,
  37655. xhr: xhrFactory()
  37656. }; // Define getter/setters for config properites
  37657. ['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) {
  37658. Object.defineProperty(Hls$1, prop, {
  37659. get: function get$$1() {
  37660. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  37661. return Config[prop];
  37662. },
  37663. set: function set$$1(value) {
  37664. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  37665. if (typeof value !== 'number' || value < 0) {
  37666. videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
  37667. return;
  37668. }
  37669. Config[prop] = value;
  37670. }
  37671. });
  37672. });
  37673. var LOCAL_STORAGE_KEY$1 = 'videojs-vhs';
  37674. var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
  37675. var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
  37676. if (mpegurlRE.test(type)) {
  37677. return 'hls';
  37678. }
  37679. var dashRE = /^application\/dash\+xml/i;
  37680. if (dashRE.test(type)) {
  37681. return 'dash';
  37682. }
  37683. return null;
  37684. };
  37685. /**
  37686. * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
  37687. *
  37688. * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
  37689. * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
  37690. * @function handleHlsMediaChange
  37691. */
  37692. var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
  37693. var newPlaylist = playlistLoader.media();
  37694. var selectedIndex = -1;
  37695. for (var i = 0; i < qualityLevels.length; i++) {
  37696. if (qualityLevels[i].id === newPlaylist.uri) {
  37697. selectedIndex = i;
  37698. break;
  37699. }
  37700. }
  37701. qualityLevels.selectedIndex_ = selectedIndex;
  37702. qualityLevels.trigger({
  37703. selectedIndex: selectedIndex,
  37704. type: 'change'
  37705. });
  37706. };
  37707. /**
  37708. * Adds quality levels to list once playlist metadata is available
  37709. *
  37710. * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
  37711. * @param {Object} hls Hls object to listen to for media events.
  37712. * @function handleHlsLoadedMetadata
  37713. */
  37714. var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
  37715. hls.representations().forEach(function (rep) {
  37716. qualityLevels.addQualityLevel(rep);
  37717. });
  37718. handleHlsMediaChange(qualityLevels, hls.playlists);
  37719. }; // HLS is a source handler, not a tech. Make sure attempts to use it
  37720. // as one do not cause exceptions.
  37721. Hls$1.canPlaySource = function () {
  37722. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  37723. };
  37724. var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
  37725. if (!keySystemOptions) {
  37726. return keySystemOptions;
  37727. } // upsert the content types based on the selected playlist
  37728. var keySystemContentTypes = {};
  37729. for (var keySystem in keySystemOptions) {
  37730. keySystemContentTypes[keySystem] = {
  37731. audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"',
  37732. videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"'
  37733. };
  37734. if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
  37735. keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
  37736. } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
  37737. // so we need to prevent overwriting the URL entirely
  37738. if (typeof keySystemOptions[keySystem] === 'string') {
  37739. keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
  37740. }
  37741. }
  37742. return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
  37743. };
  37744. var setupEmeOptions = function setupEmeOptions(hlsHandler) {
  37745. if (hlsHandler.options_.sourceType !== 'dash') {
  37746. return;
  37747. }
  37748. var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
  37749. if (player.eme) {
  37750. var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media());
  37751. if (sourceOptions) {
  37752. player.currentSource().keySystems = sourceOptions; // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449
  37753. if (player.eme.initializeMediaKeys) {
  37754. player.eme.initializeMediaKeys();
  37755. }
  37756. }
  37757. }
  37758. };
  37759. var getVhsLocalStorage = function getVhsLocalStorage() {
  37760. if (!window.localStorage) {
  37761. return null;
  37762. }
  37763. var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1);
  37764. if (!storedObject) {
  37765. return null;
  37766. }
  37767. try {
  37768. return JSON.parse(storedObject);
  37769. } catch (e) {
  37770. // someone may have tampered with the value
  37771. return null;
  37772. }
  37773. };
  37774. var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
  37775. if (!window.localStorage) {
  37776. return false;
  37777. }
  37778. var objectToStore = getVhsLocalStorage();
  37779. objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options;
  37780. try {
  37781. window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore));
  37782. } catch (e) {
  37783. // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
  37784. // storage is set to 0).
  37785. // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
  37786. // No need to perform any operation.
  37787. return false;
  37788. }
  37789. return objectToStore;
  37790. };
  37791. /**
  37792. * Whether the browser has built-in HLS support.
  37793. */
  37794. Hls$1.supportsNativeHls = function () {
  37795. var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
  37796. if (!videojs$1.getTech('Html5').isSupported()) {
  37797. return false;
  37798. } // HLS manifests can go by many mime-types
  37799. var canPlay = [// Apple santioned
  37800. 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
  37801. 'audio/mpegurl', // Very common
  37802. 'audio/x-mpegurl', // Very common
  37803. 'application/x-mpegurl', // Included for completeness
  37804. 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
  37805. return canPlay.some(function (canItPlay) {
  37806. return /maybe|probably/i.test(video.canPlayType(canItPlay));
  37807. });
  37808. }();
  37809. Hls$1.supportsNativeDash = function () {
  37810. if (!videojs$1.getTech('Html5').isSupported()) {
  37811. return false;
  37812. }
  37813. return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
  37814. }();
  37815. Hls$1.supportsTypeNatively = function (type) {
  37816. if (type === 'hls') {
  37817. return Hls$1.supportsNativeHls;
  37818. }
  37819. if (type === 'dash') {
  37820. return Hls$1.supportsNativeDash;
  37821. }
  37822. return false;
  37823. };
  37824. /**
  37825. * HLS is a source handler, not a tech. Make sure attempts to use it
  37826. * as one do not cause exceptions.
  37827. */
  37828. Hls$1.isSupported = function () {
  37829. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  37830. };
  37831. var Component$1 = videojs$1.getComponent('Component');
  37832. /**
  37833. * The Hls Handler object, where we orchestrate all of the parts
  37834. * of HLS to interact with video.js
  37835. *
  37836. * @class HlsHandler
  37837. * @extends videojs.Component
  37838. * @param {Object} source the soruce object
  37839. * @param {Tech} tech the parent tech object
  37840. * @param {Object} options optional and required options
  37841. */
  37842. var HlsHandler = function (_Component) {
  37843. inherits(HlsHandler, _Component);
  37844. function HlsHandler(source, tech, options) {
  37845. classCallCheck(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for
  37846. // backwards-compatibility
  37847. var _this = possibleConstructorReturn(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
  37848. if (tech.options_ && tech.options_.playerId) {
  37849. var _player = videojs$1(tech.options_.playerId);
  37850. if (!_player.hasOwnProperty('hls')) {
  37851. Object.defineProperty(_player, 'hls', {
  37852. get: function get$$1() {
  37853. videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.');
  37854. tech.trigger({
  37855. type: 'usage',
  37856. name: 'hls-player-access'
  37857. });
  37858. return _this;
  37859. },
  37860. configurable: true
  37861. });
  37862. } // Set up a reference to the HlsHandler from player.vhs. This allows users to start
  37863. // migrating from player.tech_.hls... to player.vhs... for API access. Although this
  37864. // isn't the most appropriate form of reference for video.js (since all APIs should
  37865. // be provided through core video.js), it is a common pattern for plugins, and vhs
  37866. // will act accordingly.
  37867. _player.vhs = _this; // deprecated, for backwards compatibility
  37868. _player.dash = _this;
  37869. _this.player_ = _player;
  37870. }
  37871. _this.tech_ = tech;
  37872. _this.source_ = source;
  37873. _this.stats = {};
  37874. _this.setOptions_();
  37875. if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
  37876. tech.overrideNativeAudioTracks(true);
  37877. tech.overrideNativeVideoTracks(true);
  37878. } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
  37879. // overriding native HLS only works if audio tracks have been emulated
  37880. // error early if we're misconfigured
  37881. throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
  37882. } // listen for fullscreenchange events for this player so that we
  37883. // can adjust our quality selection quickly
  37884. _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
  37885. var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
  37886. if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
  37887. _this.masterPlaylistController_.smoothQualityChange_();
  37888. }
  37889. }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech
  37890. _this.on(_this.tech_, 'seeking', function () {
  37891. if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) {
  37892. this.setCurrentTime(0);
  37893. }
  37894. });
  37895. _this.on(_this.tech_, 'error', function () {
  37896. if (this.masterPlaylistController_) {
  37897. this.masterPlaylistController_.pauseLoading();
  37898. }
  37899. });
  37900. _this.on(_this.tech_, 'play', _this.play);
  37901. return _this;
  37902. }
  37903. createClass(HlsHandler, [{
  37904. key: 'setOptions_',
  37905. value: function setOptions_() {
  37906. var _this2 = this; // defaults
  37907. this.options_.withCredentials = this.options_.withCredentials || false;
  37908. this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false;
  37909. this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
  37910. this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
  37911. this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
  37912. this.options_.customTagParsers = this.options_.customTagParsers || [];
  37913. this.options_.customTagMappers = this.options_.customTagMappers || [];
  37914. this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
  37915. if (typeof this.options_.blacklistDuration !== 'number') {
  37916. this.options_.blacklistDuration = 5 * 60;
  37917. }
  37918. if (typeof this.options_.bandwidth !== 'number') {
  37919. if (this.options_.useBandwidthFromLocalStorage) {
  37920. var storedObject = getVhsLocalStorage();
  37921. if (storedObject && storedObject.bandwidth) {
  37922. this.options_.bandwidth = storedObject.bandwidth;
  37923. this.tech_.trigger({
  37924. type: 'usage',
  37925. name: 'hls-bandwidth-from-local-storage'
  37926. });
  37927. }
  37928. if (storedObject && storedObject.throughput) {
  37929. this.options_.throughput = storedObject.throughput;
  37930. this.tech_.trigger({
  37931. type: 'usage',
  37932. name: 'hls-throughput-from-local-storage'
  37933. });
  37934. }
  37935. }
  37936. } // if bandwidth was not set by options or pulled from local storage, start playlist
  37937. // selection at a reasonable bandwidth
  37938. if (typeof this.options_.bandwidth !== 'number') {
  37939. this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
  37940. } // If the bandwidth number is unchanged from the initial setting
  37941. // then this takes precedence over the enableLowInitialPlaylist option
  37942. this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
  37943. ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys'].forEach(function (option) {
  37944. if (typeof _this2.source_[option] !== 'undefined') {
  37945. _this2.options_[option] = _this2.source_[option];
  37946. }
  37947. });
  37948. this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
  37949. }
  37950. /**
  37951. * called when player.src gets called, handle a new source
  37952. *
  37953. * @param {Object} src the source object to handle
  37954. */
  37955. }, {
  37956. key: 'src',
  37957. value: function src(_src, type) {
  37958. var _this3 = this; // do nothing if the src is falsey
  37959. if (!_src) {
  37960. return;
  37961. }
  37962. this.setOptions_(); // add master playlist controller options
  37963. this.options_.url = this.source_.src;
  37964. this.options_.tech = this.tech_;
  37965. this.options_.externHls = Hls$1;
  37966. this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own
  37967. // setCurrentTime function. This is needed because "seeking" events aren't always
  37968. // reliable. External seeks (via the player object) are handled via middleware.
  37969. this.options_.seekTo = function (time) {
  37970. _this3.tech_.setCurrentTime(time);
  37971. _this3.setCurrentTime(time);
  37972. };
  37973. this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
  37974. this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
  37975. seekable: function seekable$$1() {
  37976. return _this3.seekable();
  37977. },
  37978. media: function media() {
  37979. return _this3.masterPlaylistController_.media();
  37980. }
  37981. }));
  37982. this.masterPlaylistController_.on('error', function () {
  37983. var player = videojs$1.players[_this3.tech_.options_.playerId];
  37984. player.error(_this3.masterPlaylistController_.error);
  37985. }); // `this` in selectPlaylist should be the HlsHandler for backwards
  37986. // compatibility with < v2
  37987. this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
  37988. this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
  37989. this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
  37990. this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
  37991. // controller. Using a custom property for backwards compatibility
  37992. // with < v2
  37993. Object.defineProperties(this, {
  37994. selectPlaylist: {
  37995. get: function get$$1() {
  37996. return this.masterPlaylistController_.selectPlaylist;
  37997. },
  37998. set: function set$$1(selectPlaylist) {
  37999. this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
  38000. }
  38001. },
  38002. throughput: {
  38003. get: function get$$1() {
  38004. return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
  38005. },
  38006. set: function set$$1(throughput) {
  38007. this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
  38008. // for the cumulative average
  38009. this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
  38010. }
  38011. },
  38012. bandwidth: {
  38013. get: function get$$1() {
  38014. return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
  38015. },
  38016. set: function set$$1(bandwidth) {
  38017. this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
  38018. // `count` is set to zero that current value of `rate` isn't included
  38019. // in the cumulative average
  38020. this.masterPlaylistController_.mainSegmentLoader_.throughput = {
  38021. rate: 0,
  38022. count: 0
  38023. };
  38024. }
  38025. },
  38026. /**
  38027. * `systemBandwidth` is a combination of two serial processes bit-rates. The first
  38028. * is the network bitrate provided by `bandwidth` and the second is the bitrate of
  38029. * the entire process after that - decryption, transmuxing, and appending - provided
  38030. * by `throughput`.
  38031. *
  38032. * Since the two process are serial, the overall system bandwidth is given by:
  38033. * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
  38034. */
  38035. systemBandwidth: {
  38036. get: function get$$1() {
  38037. var invBandwidth = 1 / (this.bandwidth || 1);
  38038. var invThroughput = void 0;
  38039. if (this.throughput > 0) {
  38040. invThroughput = 1 / this.throughput;
  38041. } else {
  38042. invThroughput = 0;
  38043. }
  38044. var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
  38045. return systemBitrate;
  38046. },
  38047. set: function set$$1() {
  38048. videojs$1.log.error('The "systemBandwidth" property is read-only');
  38049. }
  38050. }
  38051. });
  38052. if (this.options_.bandwidth) {
  38053. this.bandwidth = this.options_.bandwidth;
  38054. }
  38055. if (this.options_.throughput) {
  38056. this.throughput = this.options_.throughput;
  38057. }
  38058. Object.defineProperties(this.stats, {
  38059. bandwidth: {
  38060. get: function get$$1() {
  38061. return _this3.bandwidth || 0;
  38062. },
  38063. enumerable: true
  38064. },
  38065. mediaRequests: {
  38066. get: function get$$1() {
  38067. return _this3.masterPlaylistController_.mediaRequests_() || 0;
  38068. },
  38069. enumerable: true
  38070. },
  38071. mediaRequestsAborted: {
  38072. get: function get$$1() {
  38073. return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
  38074. },
  38075. enumerable: true
  38076. },
  38077. mediaRequestsTimedout: {
  38078. get: function get$$1() {
  38079. return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
  38080. },
  38081. enumerable: true
  38082. },
  38083. mediaRequestsErrored: {
  38084. get: function get$$1() {
  38085. return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
  38086. },
  38087. enumerable: true
  38088. },
  38089. mediaTransferDuration: {
  38090. get: function get$$1() {
  38091. return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
  38092. },
  38093. enumerable: true
  38094. },
  38095. mediaBytesTransferred: {
  38096. get: function get$$1() {
  38097. return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
  38098. },
  38099. enumerable: true
  38100. },
  38101. mediaSecondsLoaded: {
  38102. get: function get$$1() {
  38103. return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
  38104. },
  38105. enumerable: true
  38106. },
  38107. buffered: {
  38108. get: function get$$1() {
  38109. return timeRangesToArray(_this3.tech_.buffered());
  38110. },
  38111. enumerable: true
  38112. },
  38113. currentTime: {
  38114. get: function get$$1() {
  38115. return _this3.tech_.currentTime();
  38116. },
  38117. enumerable: true
  38118. },
  38119. currentSource: {
  38120. get: function get$$1() {
  38121. return _this3.tech_.currentSource_;
  38122. },
  38123. enumerable: true
  38124. },
  38125. currentTech: {
  38126. get: function get$$1() {
  38127. return _this3.tech_.name_;
  38128. },
  38129. enumerable: true
  38130. },
  38131. duration: {
  38132. get: function get$$1() {
  38133. return _this3.tech_.duration();
  38134. },
  38135. enumerable: true
  38136. },
  38137. master: {
  38138. get: function get$$1() {
  38139. return _this3.playlists.master;
  38140. },
  38141. enumerable: true
  38142. },
  38143. playerDimensions: {
  38144. get: function get$$1() {
  38145. return _this3.tech_.currentDimensions();
  38146. },
  38147. enumerable: true
  38148. },
  38149. seekable: {
  38150. get: function get$$1() {
  38151. return timeRangesToArray(_this3.tech_.seekable());
  38152. },
  38153. enumerable: true
  38154. },
  38155. timestamp: {
  38156. get: function get$$1() {
  38157. return Date.now();
  38158. },
  38159. enumerable: true
  38160. },
  38161. videoPlaybackQuality: {
  38162. get: function get$$1() {
  38163. return _this3.tech_.getVideoPlaybackQuality();
  38164. },
  38165. enumerable: true
  38166. }
  38167. });
  38168. this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
  38169. this.tech_.on('bandwidthupdate', function () {
  38170. if (_this3.options_.useBandwidthFromLocalStorage) {
  38171. updateVhsLocalStorage({
  38172. bandwidth: _this3.bandwidth,
  38173. throughput: Math.round(_this3.throughput)
  38174. });
  38175. }
  38176. });
  38177. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  38178. // Add the manual rendition mix-in to HlsHandler
  38179. renditionSelectionMixin(_this3);
  38180. setupEmeOptions(_this3);
  38181. }); // the bandwidth of the primary segment loader is our best
  38182. // estimate of overall bandwidth
  38183. this.on(this.masterPlaylistController_, 'progress', function () {
  38184. this.tech_.trigger('progress');
  38185. });
  38186. this.tech_.ready(function () {
  38187. return _this3.setupQualityLevels_();
  38188. }); // do nothing if the tech has been disposed already
  38189. // this can occur if someone sets the src in player.ready(), for instance
  38190. if (!this.tech_.el()) {
  38191. return;
  38192. }
  38193. this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
  38194. }
  38195. /**
  38196. * Initializes the quality levels and sets listeners to update them.
  38197. *
  38198. * @method setupQualityLevels_
  38199. * @private
  38200. */
  38201. }, {
  38202. key: 'setupQualityLevels_',
  38203. value: function setupQualityLevels_() {
  38204. var _this4 = this;
  38205. var player = videojs$1.players[this.tech_.options_.playerId];
  38206. if (player && player.qualityLevels) {
  38207. this.qualityLevels_ = player.qualityLevels();
  38208. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  38209. handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
  38210. });
  38211. this.playlists.on('mediachange', function () {
  38212. handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
  38213. });
  38214. }
  38215. }
  38216. /**
  38217. * Begin playing the video.
  38218. */
  38219. }, {
  38220. key: 'play',
  38221. value: function play() {
  38222. this.masterPlaylistController_.play();
  38223. }
  38224. /**
  38225. * a wrapper around the function in MasterPlaylistController
  38226. */
  38227. }, {
  38228. key: 'setCurrentTime',
  38229. value: function setCurrentTime(currentTime) {
  38230. this.masterPlaylistController_.setCurrentTime(currentTime);
  38231. }
  38232. /**
  38233. * a wrapper around the function in MasterPlaylistController
  38234. */
  38235. }, {
  38236. key: 'duration',
  38237. value: function duration$$1() {
  38238. return this.masterPlaylistController_.duration();
  38239. }
  38240. /**
  38241. * a wrapper around the function in MasterPlaylistController
  38242. */
  38243. }, {
  38244. key: 'seekable',
  38245. value: function seekable$$1() {
  38246. return this.masterPlaylistController_.seekable();
  38247. }
  38248. /**
  38249. * Abort all outstanding work and cleanup.
  38250. */
  38251. }, {
  38252. key: 'dispose',
  38253. value: function dispose() {
  38254. if (this.playbackWatcher_) {
  38255. this.playbackWatcher_.dispose();
  38256. }
  38257. if (this.masterPlaylistController_) {
  38258. this.masterPlaylistController_.dispose();
  38259. }
  38260. if (this.qualityLevels_) {
  38261. this.qualityLevels_.dispose();
  38262. }
  38263. if (this.player_) {
  38264. delete this.player_.vhs;
  38265. delete this.player_.dash;
  38266. delete this.player_.hls;
  38267. }
  38268. if (this.tech_ && this.tech_.hls) {
  38269. delete this.tech_.hls;
  38270. }
  38271. get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
  38272. }
  38273. }, {
  38274. key: 'convertToProgramTime',
  38275. value: function convertToProgramTime(time, callback) {
  38276. return getProgramTime({
  38277. playlist: this.masterPlaylistController_.media(),
  38278. time: time,
  38279. callback: callback
  38280. });
  38281. } // the player must be playing before calling this
  38282. }, {
  38283. key: 'seekToProgramTime',
  38284. value: function seekToProgramTime$$1(programTime, callback) {
  38285. var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
  38286. var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
  38287. return seekToProgramTime({
  38288. programTime: programTime,
  38289. playlist: this.masterPlaylistController_.media(),
  38290. retryCount: retryCount,
  38291. pauseAfterSeek: pauseAfterSeek,
  38292. seekTo: this.options_.seekTo,
  38293. tech: this.options_.tech,
  38294. callback: callback
  38295. });
  38296. }
  38297. }]);
  38298. return HlsHandler;
  38299. }(Component$1);
  38300. /**
  38301. * The Source Handler object, which informs video.js what additional
  38302. * MIME types are supported and sets up playback. It is registered
  38303. * automatically to the appropriate tech based on the capabilities of
  38304. * the browser it is running in. It is not necessary to use or modify
  38305. * this object in normal usage.
  38306. */
  38307. var HlsSourceHandler = {
  38308. name: 'videojs-http-streaming',
  38309. VERSION: version$1,
  38310. canHandleSource: function canHandleSource(srcObj) {
  38311. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  38312. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  38313. return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
  38314. },
  38315. handleSource: function handleSource(source, tech) {
  38316. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  38317. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  38318. tech.hls = new HlsHandler(source, tech, localOptions);
  38319. tech.hls.xhr = xhrFactory();
  38320. tech.hls.src(source.src, source.type);
  38321. return tech.hls;
  38322. },
  38323. canPlayType: function canPlayType(type) {
  38324. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  38325. var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
  38326. overrideNative = _videojs$mergeOptions.hls.overrideNative;
  38327. var supportedType = simpleTypeFromSourceType(type);
  38328. var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
  38329. return canUseMsePlayback ? 'maybe' : '';
  38330. }
  38331. };
  38332. if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
  38333. videojs$1.MediaSource = MediaSource;
  38334. videojs$1.URL = URL$1;
  38335. } // register source handlers with the appropriate techs
  38336. if (MediaSource.supportsNativeMediaSources()) {
  38337. videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
  38338. }
  38339. videojs$1.HlsHandler = HlsHandler;
  38340. videojs$1.HlsSourceHandler = HlsSourceHandler;
  38341. videojs$1.Hls = Hls$1;
  38342. if (!videojs$1.use) {
  38343. videojs$1.registerComponent('Hls', Hls$1);
  38344. }
  38345. videojs$1.options.hls = videojs$1.options.hls || {};
  38346. if (videojs$1.registerPlugin) {
  38347. videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
  38348. } else {
  38349. videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
  38350. }
  38351. module.exports = videojs$1;