123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076 |
- /**
- * 感谢原作者提供以下代码
- * 原作者github地址
- * https://github.com/videojs
- */
- (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojsContribHls = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
- /**
- * @file bin-utils.js
- */
- /**
- * convert a TimeRange to text
- *
- * @param {TimeRange} range the timerange to use for conversion
- * @param {Number} i the iterator on the range to convert
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var textRange = function textRange(range, i) {
- return range.start(i) + '-' + range.end(i);
- };
- /**
- * format a number as hex string
- *
- * @param {Number} e The number
- * @param {Number} i the iterator
- */
- var formatHexString = function formatHexString(e, i) {
- var value = e.toString(16);
- return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
- };
- var formatAsciiString = function formatAsciiString(e) {
- if (e >= 0x20 && e < 0x7e) {
- return String.fromCharCode(e);
- }
- return '.';
- };
- /**
- * utils to help dump binary data to the console
- */
- var utils = {
- hexDump: function hexDump(data) {
- var bytes = Array.prototype.slice.call(data);
- var step = 16;
- var result = '';
- var hex = undefined;
- var ascii = undefined;
- for (var j = 0; j < bytes.length / step; j++) {
- hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
- ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
- result += hex + ' ' + ascii + '\n';
- }
- return result;
- },
- tagDump: function tagDump(tag) {
- return utils.hexDump(tag.bytes);
- },
- textRanges: function textRanges(ranges) {
- var result = '';
- var i = undefined;
- for (i = 0; i < ranges.length; i++) {
- result += textRange(ranges, i) + ' ';
- }
- return result;
- }
- };
- exports['default'] = utils;
- module.exports = exports['default'];
- },{}],2:[function(require,module,exports){
- /**
- * @file decrypter/aes.js
- *
- * This file contains an adaptation of the AES decryption algorithm
- * from the Standford Javascript Cryptography Library. That work is
- * covered by the following copyright and permissions notice:
- *
- * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
- * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and documentation
- * are those of the authors and should not be interpreted as representing
- * official policies, either expressed or implied, of the authors.
- */
- /**
- * Expand the S-box tables.
- *
- * @private
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- var precompute = function precompute() {
- var tables = [[[], [], [], [], []], [[], [], [], [], []]];
- var encTable = tables[0];
- var decTable = tables[1];
- var sbox = encTable[4];
- var sboxInv = decTable[4];
- var i = undefined;
- var x = undefined;
- var xInv = undefined;
- var d = [];
- var th = [];
- var x2 = undefined;
- var x4 = undefined;
- var x8 = undefined;
- var s = undefined;
- var tEnc = undefined;
- var tDec = undefined;
- // Compute double and third tables
- for (i = 0; i < 256; i++) {
- th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
- }
- for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
- // Compute sbox
- s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
- s = s >> 8 ^ s & 255 ^ 99;
- sbox[x] = s;
- sboxInv[s] = x;
- // Compute MixColumns
- x8 = d[x4 = d[x2 = d[x]]];
- tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
- tEnc = d[s] * 0x101 ^ s * 0x1010100;
- for (i = 0; i < 4; i++) {
- encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
- decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
- }
- }
- // Compactify. Considerable speedup on Firefox.
- for (i = 0; i < 5; i++) {
- encTable[i] = encTable[i].slice(0);
- decTable[i] = decTable[i].slice(0);
- }
- return tables;
- };
- var aesTables = null;
- /**
- * Schedule out an AES key for both encryption and decryption. This
- * is a low-level class. Use a cipher mode to do bulk encryption.
- *
- * @class AES
- * @param key {Array} The key as an array of 4, 6 or 8 words.
- */
- var AES = (function () {
- function AES(key) {
- _classCallCheck(this, AES);
- /**
- * The expanded S-box and inverse S-box tables. These will be computed
- * on the client so that we don't have to send them down the wire.
- *
- * There are two tables, _tables[0] is for encryption and
- * _tables[1] is for decryption.
- *
- * The first 4 sub-tables are the expanded S-box with MixColumns. The
- * last (_tables[01][4]) is the S-box itself.
- *
- * @private
- */
- // if we have yet to precompute the S-box tables
- // do so now
- if (!aesTables) {
- aesTables = precompute();
- }
- // then make a copy of that object for use
- 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()]];
- var i = undefined;
- var j = undefined;
- var tmp = undefined;
- var encKey = undefined;
- var decKey = undefined;
- var sbox = this._tables[0][4];
- var decTable = this._tables[1];
- var keyLen = key.length;
- var rcon = 1;
- if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
- throw new Error('Invalid aes key size');
- }
- encKey = key.slice(0);
- decKey = [];
- this._key = [encKey, decKey];
- // schedule encryption keys
- for (i = keyLen; i < 4 * keyLen + 28; i++) {
- tmp = encKey[i - 1];
- // apply sbox
- if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
- tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255];
- // shift rows and add rcon
- if (i % keyLen === 0) {
- tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
- rcon = rcon << 1 ^ (rcon >> 7) * 283;
- }
- }
- encKey[i] = encKey[i - keyLen] ^ tmp;
- }
- // schedule decryption keys
- for (j = 0; i; j++, i--) {
- tmp = encKey[j & 3 ? i : i - 4];
- if (i <= 4 || j < 4) {
- decKey[j] = tmp;
- } else {
- decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
- }
- }
- }
- /**
- * Decrypt 16 bytes, specified as four 32-bit words.
- *
- * @param {Number} encrypted0 the first word to decrypt
- * @param {Number} encrypted1 the second word to decrypt
- * @param {Number} encrypted2 the third word to decrypt
- * @param {Number} encrypted3 the fourth word to decrypt
- * @param {Int32Array} out the array to write the decrypted words
- * into
- * @param {Number} offset the offset into the output array to start
- * writing results
- * @return {Array} The plaintext.
- */
- _createClass(AES, [{
- key: 'decrypt',
- value: function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
- var key = this._key[1];
- // state variables a,b,c,d are loaded with pre-whitened data
- var a = encrypted0 ^ key[0];
- var b = encrypted3 ^ key[1];
- var c = encrypted2 ^ key[2];
- var d = encrypted1 ^ key[3];
- var a2 = undefined;
- var b2 = undefined;
- var c2 = undefined;
- // key.length === 2 ?
- var nInnerRounds = key.length / 4 - 2;
- var i = undefined;
- var kIndex = 4;
- var table = this._tables[1];
- // load up the tables
- var table0 = table[0];
- var table1 = table[1];
- var table2 = table[2];
- var table3 = table[3];
- var sbox = table[4];
- // Inner rounds. Cribbed from OpenSSL.
- for (i = 0; i < nInnerRounds; i++) {
- a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
- b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
- c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
- d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
- kIndex += 4;
- a = a2;b = b2;c = c2;
- }
- // Last round.
- for (i = 0; i < 4; i++) {
- out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
- a2 = a;a = b;b = c;c = d;d = a2;
- }
- }
- }]);
- return AES;
- })();
- exports['default'] = AES;
- module.exports = exports['default'];
- },{}],3:[function(require,module,exports){
- /**
- * @file decrypter/async-stream.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _stream = require('../stream');
- var _stream2 = _interopRequireDefault(_stream);
- /**
- * A wrapper around the Stream class to use setTiemout
- * and run stream "jobs" Asynchronously
- *
- * @class AsyncStream
- * @extends Stream
- */
- var AsyncStream = (function (_Stream) {
- _inherits(AsyncStream, _Stream);
- function AsyncStream() {
- _classCallCheck(this, AsyncStream);
- _get(Object.getPrototypeOf(AsyncStream.prototype), 'constructor', this).call(this, _stream2['default']);
- this.jobs = [];
- this.delay = 1;
- this.timeout_ = null;
- }
- /**
- * process an async job
- *
- * @private
- */
- _createClass(AsyncStream, [{
- key: 'processJob_',
- value: function processJob_() {
- this.jobs.shift()();
- if (this.jobs.length) {
- this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
- } else {
- this.timeout_ = null;
- }
- }
- /**
- * push a job into the stream
- *
- * @param {Function} job the job to push into the stream
- */
- }, {
- key: 'push',
- value: function push(job) {
- this.jobs.push(job);
- if (!this.timeout_) {
- this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
- }
- }
- }]);
- return AsyncStream;
- })(_stream2['default']);
- exports['default'] = AsyncStream;
- module.exports = exports['default'];
- },{"../stream":18}],4:[function(require,module,exports){
- /**
- * @file decrypter/decrypter.js
- *
- * An asynchronous implementation of AES-128 CBC decryption with
- * PKCS#7 padding.
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- var _aes = require('./aes');
- var _aes2 = _interopRequireDefault(_aes);
- var _asyncStream = require('./async-stream');
- var _asyncStream2 = _interopRequireDefault(_asyncStream);
- var _pkcs7 = require('pkcs7');
- /**
- * Convert network-order (big-endian) bytes into their little-endian
- * representation.
- */
- var ntoh = function ntoh(word) {
- return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
- };
- /**
- * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
- *
- * @param {Uint8Array} encrypted the encrypted bytes
- * @param {Uint32Array} key the bytes of the decryption key
- * @param {Uint32Array} initVector the initialization vector (IV) to
- * use for the first round of CBC.
- * @return {Uint8Array} the decrypted bytes
- *
- * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
- * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
- * @see https://tools.ietf.org/html/rfc2315
- */
- var decrypt = function decrypt(encrypted, key, initVector) {
- // word-level access to the encrypted bytes
- var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
- var decipher = new _aes2['default'](Array.prototype.slice.call(key));
- // byte and word-level access for the decrypted output
- var decrypted = new Uint8Array(encrypted.byteLength);
- var decrypted32 = new Int32Array(decrypted.buffer);
- // temporary variables for working with the IV, encrypted, and
- // decrypted data
- var init0 = undefined;
- var init1 = undefined;
- var init2 = undefined;
- var init3 = undefined;
- var encrypted0 = undefined;
- var encrypted1 = undefined;
- var encrypted2 = undefined;
- var encrypted3 = undefined;
- // iteration variable
- var wordIx = undefined;
- // pull out the words of the IV to ensure we don't modify the
- // passed-in reference and easier access
- init0 = initVector[0];
- init1 = initVector[1];
- init2 = initVector[2];
- init3 = initVector[3];
- // decrypt four word sequences, applying cipher-block chaining (CBC)
- // to each decrypted block
- for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
- // convert big-endian (network order) words into little-endian
- // (javascript order)
- encrypted0 = ntoh(encrypted32[wordIx]);
- encrypted1 = ntoh(encrypted32[wordIx + 1]);
- encrypted2 = ntoh(encrypted32[wordIx + 2]);
- encrypted3 = ntoh(encrypted32[wordIx + 3]);
- // decrypt the block
- decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx);
- // XOR with the IV, and restore network byte-order to obtain the
- // plaintext
- decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
- decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
- decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
- decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
- // setup the IV for the next round
- init0 = encrypted0;
- init1 = encrypted1;
- init2 = encrypted2;
- init3 = encrypted3;
- }
- return decrypted;
- };
- exports.decrypt = decrypt;
- /**
- * The `Decrypter` class that manages decryption of AES
- * data through `AsyncStream` objects and the `decrypt`
- * function
- *
- * @param {Uint8Array} encrypted the encrypted bytes
- * @param {Uint32Array} key the bytes of the decryption key
- * @param {Uint32Array} initVector the initialization vector (IV) to
- * @param {Function} done the function to run when done
- * @class Decrypter
- */
- var Decrypter = (function () {
- function Decrypter(encrypted, key, initVector, done) {
- _classCallCheck(this, Decrypter);
- var step = Decrypter.STEP;
- var encrypted32 = new Int32Array(encrypted.buffer);
- var decrypted = new Uint8Array(encrypted.byteLength);
- var i = 0;
- this.asyncStream_ = new _asyncStream2['default']();
- // split up the encryption job and do the individual chunks asynchronously
- this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
- for (i = step; i < encrypted32.length; i += step) {
- initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
- this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
- }
- // invoke the done() callback when everything is finished
- this.asyncStream_.push(function () {
- // remove pkcs#7 padding from the decrypted bytes
- done(null, (0, _pkcs7.unpad)(decrypted));
- });
- }
- /**
- * a getter for step the maximum number of bytes to process at one time
- *
- * @return {Number} the value of step 32000
- */
- _createClass(Decrypter, [{
- key: 'decryptChunk_',
- /**
- * @private
- */
- value: function decryptChunk_(encrypted, key, initVector, decrypted) {
- return function () {
- var bytes = decrypt(encrypted, key, initVector);
- decrypted.set(bytes, encrypted.byteOffset);
- };
- }
- }], [{
- key: 'STEP',
- get: function get() {
- // 4 * 8000;
- return 32000;
- }
- }]);
- return Decrypter;
- })();
- exports.Decrypter = Decrypter;
- exports['default'] = {
- Decrypter: Decrypter,
- decrypt: decrypt
- };
- },{"./aes":2,"./async-stream":3,"pkcs7":23}],5:[function(require,module,exports){
- /**
- * @file decrypter/index.js
- *
- * Index module to easily import the primary components of AES-128
- * decryption. Like this:
- *
- * ```js
- * import {Decrypter, decrypt, AsyncStream} from './src/decrypter';
- * ```
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _decrypter = require('./decrypter');
- var _asyncStream = require('./async-stream');
- var _asyncStream2 = _interopRequireDefault(_asyncStream);
- exports['default'] = {
- decrypt: _decrypter.decrypt,
- Decrypter: _decrypter.Decrypter,
- AsyncStream: _asyncStream2['default']
- };
- module.exports = exports['default'];
- },{"./async-stream":3,"./decrypter":4}],6:[function(require,module,exports){
- (function (global){
- /**
- * @file hls-audio-track.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _playlistLoader = require('./playlist-loader');
- var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
- /**
- * HlsAudioTrack extends video.js audio tracks but adds HLS
- * specific data storage such as playlist loaders, mediaGroups
- * and default/autoselect
- *
- * @param {Object} options options to create HlsAudioTrack with
- * @class HlsAudioTrack
- * @extends AudioTrack
- */
- var HlsAudioTrack = (function (_AudioTrack) {
- _inherits(HlsAudioTrack, _AudioTrack);
- function HlsAudioTrack(options) {
- _classCallCheck(this, HlsAudioTrack);
- _get(Object.getPrototypeOf(HlsAudioTrack.prototype), 'constructor', this).call(this, {
- kind: options['default'] ? 'main' : 'alternative',
- enabled: options['default'] || false,
- language: options.language,
- label: options.label
- });
- this.hls = options.hls;
- this.autoselect = options.autoselect || false;
- this['default'] = options['default'] || false;
- this.withCredentials = options.withCredentials || false;
- this.mediaGroups_ = [];
- this.addLoader(options.mediaGroup, options.resolvedUri);
- }
- /**
- * get a PlaylistLoader from this track given a mediaGroup name
- *
- * @param {String} mediaGroup the mediaGroup to get the loader for
- * @return {PlaylistLoader|Null} the PlaylistLoader or null
- */
- _createClass(HlsAudioTrack, [{
- key: 'getLoader',
- value: function getLoader(mediaGroup) {
- for (var i = 0; i < this.mediaGroups_.length; i++) {
- var mgl = this.mediaGroups_[i];
- if (mgl.mediaGroup === mediaGroup) {
- return mgl.loader;
- }
- }
- }
- /**
- * add a PlaylistLoader given a mediaGroup, and a uri. for a combined track
- * we store null for the playlistloader
- *
- * @param {String} mediaGroup the mediaGroup to get the loader for
- * @param {String} uri the uri to get the audio track/mediaGroup from
- */
- }, {
- key: 'addLoader',
- value: function addLoader(mediaGroup) {
- var uri = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
- var loader = null;
- if (uri) {
- // TODO: this should probably happen upstream in Master Playlist
- // Controller when we can switch PlaylistLoader sources
- // then we can just store the uri here instead
- loader = new _playlistLoader2['default'](uri, this.hls, this.withCredentials);
- }
- this.mediaGroups_.push({ mediaGroup: mediaGroup, loader: loader });
- }
- /**
- * remove a playlist loader from a track given the mediaGroup
- *
- * @param {String} mediaGroup the mediaGroup to remove
- */
- }, {
- key: 'removeLoader',
- value: function removeLoader(mediaGroup) {
- for (var i = 0; i < this.mediaGroups_.length; i++) {
- var mgl = this.mediaGroups_[i];
- if (mgl.mediaGroup === mediaGroup) {
- if (mgl.loader) {
- mgl.loader.dispose();
- }
- this.mediaGroups_.splice(i, 1);
- return;
- }
- }
- }
- /**
- * Dispose of this audio track and
- * the playlist loader that it holds inside
- */
- }, {
- key: 'dispose',
- value: function dispose() {
- var i = this.mediaGroups_.length;
- while (i--) {
- this.removeLoader(this.mediaGroups_[i].mediaGroup);
- }
- }
- }]);
- return HlsAudioTrack;
- })(_videoJs.AudioTrack);
- exports['default'] = HlsAudioTrack;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./playlist-loader":12}],7:[function(require,module,exports){
- /**
- * @file m3u8/index.js
- *
- * Utilities for parsing M3U8 files. If the entire manifest is available,
- * `Parser` will create an object representation with enough detail for managing
- * playback. `ParseStream` and `LineStream` are lower-level parsing primitives
- * that do not assume the entirety of the manifest is ready and expose a
- * ReadableStream-like interface.
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _lineStream = require('./line-stream');
- var _lineStream2 = _interopRequireDefault(_lineStream);
- var _parseStream = require('./parse-stream');
- var _parseStream2 = _interopRequireDefault(_parseStream);
- var _parser = require('./parser');
- var _parser2 = _interopRequireDefault(_parser);
- exports['default'] = {
- LineStream: _lineStream2['default'],
- ParseStream: _parseStream2['default'],
- Parser: _parser2['default']
- };
- module.exports = exports['default'];
- },{"./line-stream":8,"./parse-stream":9,"./parser":10}],8:[function(require,module,exports){
- /**
- * @file m3u8/line-stream.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _stream = require('../stream');
- var _stream2 = _interopRequireDefault(_stream);
- /**
- * A stream that buffers string input and generates a `data` event for each
- * line.
- *
- * @class LineStream
- * @extends Stream
- */
- var LineStream = (function (_Stream) {
- _inherits(LineStream, _Stream);
- function LineStream() {
- _classCallCheck(this, LineStream);
- _get(Object.getPrototypeOf(LineStream.prototype), 'constructor', this).call(this);
- this.buffer = '';
- }
- /**
- * Add new data to be parsed.
- *
- * @param {String} data the text to process
- */
- _createClass(LineStream, [{
- key: 'push',
- value: function push(data) {
- var nextNewline = undefined;
- this.buffer += data;
- nextNewline = this.buffer.indexOf('\n');
- for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
- this.trigger('data', this.buffer.substring(0, nextNewline));
- this.buffer = this.buffer.substring(nextNewline + 1);
- }
- }
- }]);
- return LineStream;
- })(_stream2['default']);
- exports['default'] = LineStream;
- module.exports = exports['default'];
- },{"../stream":18}],9:[function(require,module,exports){
- /**
- * @file m3u8/parse-stream.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _stream = require('../stream');
- var _stream2 = _interopRequireDefault(_stream);
- /**
- * "forgiving" attribute list psuedo-grammar:
- * attributes -> keyvalue (',' keyvalue)*
- * keyvalue -> key '=' value
- * key -> [^=]*
- * value -> '"' [^"]* '"' | [^,]*
- */
- var attributeSeparator = function attributeSeparator() {
- var key = '[^=]*';
- var value = '"[^"]*"|[^,]*';
- var keyvalue = '(?:' + key + ')=(?:' + value + ')';
- return new RegExp('(?:^|,)(' + keyvalue + ')');
- };
- /**
- * Parse attributes from a line given the seperator
- *
- * @param {String} attributes the attibute line to parse
- */
- var parseAttributes = function parseAttributes(attributes) {
- // split the string using attributes as the separator
- var attrs = attributes.split(attributeSeparator());
- var i = attrs.length;
- var result = {};
- var attr = undefined;
- while (i--) {
- // filter out unmatched portions of the string
- if (attrs[i] === '') {
- continue;
- }
- // split the key and value
- attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1);
- // trim whitespace and remove optional quotes around the value
- attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
- attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
- attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
- result[attr[0]] = attr[1];
- }
- return result;
- };
- /**
- * A line-level M3U8 parser event stream. It expects to receive input one
- * line at a time and performs a context-free parse of its contents. A stream
- * interpretation of a manifest can be useful if the manifest is expected to
- * be too large to fit comfortably into memory or the entirety of the input
- * is not immediately available. Otherwise, it's probably much easier to work
- * with a regular `Parser` object.
- *
- * Produces `data` events with an object that captures the parser's
- * interpretation of the input. That object has a property `tag` that is one
- * of `uri`, `comment`, or `tag`. URIs only have a single additional
- * property, `line`, which captures the entirety of the input without
- * interpretation. Comments similarly have a single additional property
- * `text` which is the input without the leading `#`.
- *
- * Tags always have a property `tagType` which is the lower-cased version of
- * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
- * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
- * tags are given the tag type `unknown` and a single additional property
- * `data` with the remainder of the input.
- *
- * @class ParseStream
- * @extends Stream
- */
- var ParseStream = (function (_Stream) {
- _inherits(ParseStream, _Stream);
- function ParseStream() {
- _classCallCheck(this, ParseStream);
- _get(Object.getPrototypeOf(ParseStream.prototype), 'constructor', this).call(this);
- }
- /**
- * Parses an additional line of input.
- *
- * @param {String} line a single line of an M3U8 file to parse
- */
- _createClass(ParseStream, [{
- key: 'push',
- value: function push(line) {
- var match = undefined;
- var event = undefined;
- // strip whitespace
- line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, '');
- if (line.length === 0) {
- // ignore empty lines
- return;
- }
- // URIs
- if (line[0] !== '#') {
- this.trigger('data', {
- type: 'uri',
- uri: line
- });
- return;
- }
- // Comments
- if (line.indexOf('#EXT') !== 0) {
- this.trigger('data', {
- type: 'comment',
- text: line.slice(1)
- });
- return;
- }
- // strip off any carriage returns here so the regex matching
- // doesn't have to account for them.
- line = line.replace('\r', '');
- // Tags
- match = /^#EXTM3U/.exec(line);
- if (match) {
- this.trigger('data', {
- type: 'tag',
- tagType: 'm3u'
- });
- return;
- }
- match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'inf'
- };
- if (match[1]) {
- event.duration = parseFloat(match[1]);
- }
- if (match[2]) {
- event.title = match[2];
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'targetduration'
- };
- if (match[1]) {
- event.duration = parseInt(match[1], 10);
- }
- this.trigger('data', event);
- return;
- }
- match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'totalduration'
- };
- if (match[1]) {
- event.duration = parseInt(match[1], 10);
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'version'
- };
- if (match[1]) {
- event.version = parseInt(match[1], 10);
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'media-sequence'
- };
- if (match[1]) {
- event.number = parseInt(match[1], 10);
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'discontinuity-sequence'
- };
- if (match[1]) {
- event.number = parseInt(match[1], 10);
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'playlist-type'
- };
- if (match[1]) {
- event.playlistType = match[1];
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'byterange'
- };
- if (match[1]) {
- event.length = parseInt(match[1], 10);
- }
- if (match[2]) {
- event.offset = parseInt(match[2], 10);
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'allow-cache'
- };
- if (match[1]) {
- event.allowed = !/NO/.test(match[1]);
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'stream-inf'
- };
- if (match[1]) {
- event.attributes = parseAttributes(match[1]);
- if (event.attributes.RESOLUTION) {
- var split = event.attributes.RESOLUTION.split('x');
- var resolution = {};
- if (split[0]) {
- resolution.width = parseInt(split[0], 10);
- }
- if (split[1]) {
- resolution.height = parseInt(split[1], 10);
- }
- event.attributes.RESOLUTION = resolution;
- }
- if (event.attributes.BANDWIDTH) {
- event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
- }
- if (event.attributes['PROGRAM-ID']) {
- event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
- }
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-MEDIA:?(.*)$/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'media'
- };
- if (match[1]) {
- event.attributes = parseAttributes(match[1]);
- }
- this.trigger('data', event);
- return;
- }
- match = /^#EXT-X-ENDLIST/.exec(line);
- if (match) {
- this.trigger('data', {
- type: 'tag',
- tagType: 'endlist'
- });
- return;
- }
- match = /^#EXT-X-DISCONTINUITY/.exec(line);
- if (match) {
- this.trigger('data', {
- type: 'tag',
- tagType: 'discontinuity'
- });
- return;
- }
- match = /^#EXT-X-KEY:?(.*)$/.exec(line);
- if (match) {
- event = {
- type: 'tag',
- tagType: 'key'
- };
- if (match[1]) {
- event.attributes = parseAttributes(match[1]);
- // parse the IV string into a Uint32Array
- if (event.attributes.IV) {
- if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
- event.attributes.IV = event.attributes.IV.substring(2);
- }
- event.attributes.IV = event.attributes.IV.match(/.{8}/g);
- event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
- event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
- event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
- event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
- event.attributes.IV = new Uint32Array(event.attributes.IV);
- }
- }
- this.trigger('data', event);
- return;
- }
- // unknown tag type
- this.trigger('data', {
- type: 'tag',
- data: line.slice(4, line.length)
- });
- }
- }]);
- return ParseStream;
- })(_stream2['default']);
- exports['default'] = ParseStream;
- module.exports = exports['default'];
- },{"../stream":18}],10:[function(require,module,exports){
- (function (global){
- /**
- * @file m3u8/parser.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _stream = require('../stream');
- var _stream2 = _interopRequireDefault(_stream);
- var _lineStream = require('./line-stream');
- var _lineStream2 = _interopRequireDefault(_lineStream);
- var _parseStream = require('./parse-stream');
- var _parseStream2 = _interopRequireDefault(_parseStream);
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- /**
- * A parser for M3U8 files. The current interpretation of the input is
- * exposed as a property `manifest` on parser objects. It's just two lines to
- * create and parse a manifest once you have the contents available as a string:
- *
- * ```js
- * var parser = new videojs.m3u8.Parser();
- * parser.push(xhr.responseText);
- * ```
- *
- * New input can later be applied to update the manifest object by calling
- * `push` again.
- *
- * The parser attempts to create a usable manifest object even if the
- * underlying input is somewhat nonsensical. It emits `info` and `warning`
- * events during the parse if it encounters input that seems invalid or
- * requires some property of the manifest object to be defaulted.
- *
- * @class Parser
- * @extends Stream
- */
- var Parser = (function (_Stream) {
- _inherits(Parser, _Stream);
- function Parser() {
- _classCallCheck(this, Parser);
- _get(Object.getPrototypeOf(Parser.prototype), 'constructor', this).call(this);
- this.lineStream = new _lineStream2['default']();
- this.parseStream = new _parseStream2['default']();
- this.lineStream.pipe(this.parseStream);
- /* eslint-disable consistent-this */
- var self = this;
- /* eslint-enable consistent-this */
- var uris = [];
- var currentUri = {};
- var _key = undefined;
- var noop = function noop() {};
- var defaultMediaGroups = {
- 'AUDIO': {},
- 'VIDEO': {},
- 'CLOSED-CAPTIONS': {},
- 'SUBTITLES': {}
- };
- // group segments into numbered timelines delineated by discontinuities
- var currentTimeline = 0;
- // the manifest is empty until the parse stream begins delivering data
- this.manifest = {
- allowCache: true,
- discontinuityStarts: []
- };
- // update the manifest with the m3u8 entry from the parse stream
- this.parseStream.on('data', function (entry) {
- var mediaGroup = undefined;
- var rendition = undefined;
- ({
- tag: function tag() {
- // switch based on the tag type
- (({
- 'allow-cache': function allowCache() {
- this.manifest.allowCache = entry.allowed;
- if (!('allowed' in entry)) {
- this.trigger('info', {
- message: 'defaulting allowCache to YES'
- });
- this.manifest.allowCache = true;
- }
- },
- byterange: function byterange() {
- var byterange = {};
- if ('length' in entry) {
- currentUri.byterange = byterange;
- byterange.length = entry.length;
- if (!('offset' in entry)) {
- this.trigger('info', {
- message: 'defaulting offset to zero'
- });
- entry.offset = 0;
- }
- }
- if ('offset' in entry) {
- currentUri.byterange = byterange;
- byterange.offset = entry.offset;
- }
- },
- endlist: function endlist() {
- this.manifest.endList = true;
- },
- inf: function inf() {
- if (!('mediaSequence' in this.manifest)) {
- this.manifest.mediaSequence = 0;
- this.trigger('info', {
- message: 'defaulting media sequence to zero'
- });
- }
- if (!('discontinuitySequence' in this.manifest)) {
- this.manifest.discontinuitySequence = 0;
- this.trigger('info', {
- message: 'defaulting discontinuity sequence to zero'
- });
- }
- if (entry.duration >= 0) {
- currentUri.duration = entry.duration;
- }
- this.manifest.segments = uris;
- },
- key: function key() {
- if (!entry.attributes) {
- this.trigger('warn', {
- message: 'ignoring key declaration without attribute list'
- });
- return;
- }
- // clear the active encryption key
- if (entry.attributes.METHOD === 'NONE') {
- _key = null;
- return;
- }
- if (!entry.attributes.URI) {
- this.trigger('warn', {
- message: 'ignoring key declaration without URI'
- });
- return;
- }
- if (!entry.attributes.METHOD) {
- this.trigger('warn', {
- message: 'defaulting key method to AES-128'
- });
- }
- // setup an encryption key for upcoming segments
- _key = {
- method: entry.attributes.METHOD || 'AES-128',
- uri: entry.attributes.URI
- };
- if (typeof entry.attributes.IV !== 'undefined') {
- _key.iv = entry.attributes.IV;
- }
- },
- 'media-sequence': function mediaSequence() {
- if (!isFinite(entry.number)) {
- this.trigger('warn', {
- message: 'ignoring invalid media sequence: ' + entry.number
- });
- return;
- }
- this.manifest.mediaSequence = entry.number;
- },
- 'discontinuity-sequence': function discontinuitySequence() {
- if (!isFinite(entry.number)) {
- this.trigger('warn', {
- message: 'ignoring invalid discontinuity sequence: ' + entry.number
- });
- return;
- }
- this.manifest.discontinuitySequence = entry.number;
- currentTimeline = entry.number;
- },
- 'playlist-type': function playlistType() {
- if (!/VOD|EVENT/.test(entry.playlistType)) {
- this.trigger('warn', {
- message: 'ignoring unknown playlist type: ' + entry.playlist
- });
- return;
- }
- this.manifest.playlistType = entry.playlistType;
- },
- 'stream-inf': function streamInf() {
- this.manifest.playlists = uris;
- this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
- if (!entry.attributes) {
- this.trigger('warn', {
- message: 'ignoring empty stream-inf attributes'
- });
- return;
- }
- if (!currentUri.attributes) {
- currentUri.attributes = {};
- }
- currentUri.attributes = (0, _videoJs.mergeOptions)(currentUri.attributes, entry.attributes);
- },
- media: function media() {
- this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
- if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
- this.trigger('warn', {
- message: 'ignoring incomplete or missing media group'
- });
- return;
- }
- // find the media group, creating defaults as necessary
- var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
- mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
- mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']];
- // collect the rendition metadata
- rendition = {
- 'default': /yes/i.test(entry.attributes.DEFAULT)
- };
- if (rendition['default']) {
- rendition.autoselect = true;
- } else {
- rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
- }
- if (entry.attributes.LANGUAGE) {
- rendition.language = entry.attributes.LANGUAGE;
- }
- if (entry.attributes.URI) {
- rendition.uri = entry.attributes.URI;
- }
- // insert the new rendition
- mediaGroup[entry.attributes.NAME] = rendition;
- },
- discontinuity: function discontinuity() {
- currentTimeline += 1;
- currentUri.discontinuity = true;
- this.manifest.discontinuityStarts.push(uris.length);
- },
- targetduration: function targetduration() {
- if (!isFinite(entry.duration) || entry.duration < 0) {
- this.trigger('warn', {
- message: 'ignoring invalid target duration: ' + entry.duration
- });
- return;
- }
- this.manifest.targetDuration = entry.duration;
- },
- totalduration: function totalduration() {
- if (!isFinite(entry.duration) || entry.duration < 0) {
- this.trigger('warn', {
- message: 'ignoring invalid total duration: ' + entry.duration
- });
- return;
- }
- this.manifest.totalDuration = entry.duration;
- }
- })[entry.tagType] || noop).call(self);
- },
- uri: function uri() {
- currentUri.uri = entry.uri;
- uris.push(currentUri);
- // if no explicit duration was declared, use the target duration
- if (this.manifest.targetDuration && !('duration' in currentUri)) {
- this.trigger('warn', {
- message: 'defaulting segment duration to the target duration'
- });
- currentUri.duration = this.manifest.targetDuration;
- }
- // annotate with encryption information, if necessary
- if (_key) {
- currentUri.key = _key;
- }
- currentUri.timeline = currentTimeline;
- // prepare for the next URI
- currentUri = {};
- },
- comment: function comment() {
- // comments are not important for playback
- }
- })[entry.type].call(self);
- });
- }
- /**
- * Parse the input string and update the manifest object.
- *
- * @param {String} chunk a potentially incomplete portion of the manifest
- */
- _createClass(Parser, [{
- key: 'push',
- value: function push(chunk) {
- this.lineStream.push(chunk);
- }
- /**
- * Flush any remaining input. This can be handy if the last line of an M3U8
- * manifest did not contain a trailing newline but the file has been
- * completely received.
- */
- }, {
- key: 'end',
- value: function end() {
- // flush any buffered input
- this.lineStream.push('\n');
- }
- }]);
- return Parser;
- })(_stream2['default']);
- exports['default'] = Parser;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"../stream":18,"./line-stream":8,"./parse-stream":9}],11:[function(require,module,exports){
- (function (global){
- /**
- * @file master-playlist-controller.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _playlistLoader = require('./playlist-loader');
- var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
- var _segmentLoader = require('./segment-loader');
- var _segmentLoader2 = _interopRequireDefault(_segmentLoader);
- var _ranges = require('./ranges');
- var _ranges2 = _interopRequireDefault(_ranges);
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- var _hlsAudioTrack = require('./hls-audio-track');
- var _hlsAudioTrack2 = _interopRequireDefault(_hlsAudioTrack);
- // 5 minute blacklist
- var BLACKLIST_DURATION = 5 * 60 * 1000;
- var Hls = undefined;
- var parseCodecs = function parseCodecs(codecs) {
- var result = {
- codecCount: 0,
- videoCodec: null,
- audioProfile: null
- };
- result.codecCount = codecs.split(',').length;
- result.codecCount = result.codecCount || 2;
- // parse the video codec but ignore the version
- result.videoCodec = /(^|\s|,)+(avc1)[^ ,]*/i.exec(codecs);
- result.videoCodec = result.videoCodec && result.videoCodec[2];
- // parse the last field of the audio codec
- result.audioProfile = /(^|\s|,)+mp4a.\d+\.(\d+)/i.exec(codecs);
- result.audioProfile = result.audioProfile && result.audioProfile[2];
- return result;
- };
- /**
- * the master playlist controller controller all interactons
- * between playlists and segmentloaders. At this time this mainly
- * involves a master playlist and a series of audio playlists
- * if they are available
- *
- * @class MasterPlaylistController
- * @extends videojs.EventTarget
- */
- var MasterPlaylistController = (function (_videojs$EventTarget) {
- _inherits(MasterPlaylistController, _videojs$EventTarget);
- function MasterPlaylistController(_ref) {
- var _this = this;
- var url = _ref.url;
- var withCredentials = _ref.withCredentials;
- var mode = _ref.mode;
- var tech = _ref.tech;
- var bandwidth = _ref.bandwidth;
- var externHls = _ref.externHls;
- _classCallCheck(this, MasterPlaylistController);
- _get(Object.getPrototypeOf(MasterPlaylistController.prototype), 'constructor', this).call(this);
- Hls = externHls;
- this.withCredentials = withCredentials;
- this.tech_ = tech;
- this.hls_ = tech.hls;
- this.mode_ = mode;
- this.audioTracks_ = [];
- this.mediaSource = new _videoJs2['default'].MediaSource({ mode: mode });
- this.mediaSource.on('audioinfo', function (e) {
- return _this.trigger(e);
- });
- // load the media source into the player
- this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_.bind(this));
- var segmentLoaderOptions = {
- hls: this.hls_,
- mediaSource: this.mediaSource,
- currentTime: this.tech_.currentTime.bind(this.tech_),
- withCredentials: this.withCredentials,
- seekable: function seekable() {
- return _this.seekable();
- },
- seeking: function seeking() {
- return _this.tech_.seeking();
- },
- setCurrentTime: function setCurrentTime(a) {
- return _this.setCurrentTime(a);
- },
- hasPlayed: function hasPlayed() {
- return _this.tech_.played().length !== 0;
- },
- bandwidth: bandwidth
- };
- // combined audio/video or just video when alternate audio track is selected
- this.mainSegmentLoader_ = new _segmentLoader2['default'](segmentLoaderOptions);
- // alternate audio track
- this.audioSegmentLoader_ = new _segmentLoader2['default'](segmentLoaderOptions);
- if (!url) {
- throw new Error('A non-empty playlist URL is required');
- }
- this.masterPlaylistLoader_ = new _playlistLoader2['default'](url, this.hls_, this.withCredentials);
- this.masterPlaylistLoader_.on('loadedmetadata', function () {
- var media = _this.masterPlaylistLoader_.media();
- // if this isn't a live video and preload permits, start
- // downloading segments
- if (media.endList && _this.tech_.preload() !== 'none') {
- _this.mainSegmentLoader_.playlist(media);
- _this.mainSegmentLoader_.expired(_this.masterPlaylistLoader_.expired_);
- _this.mainSegmentLoader_.load();
- }
- _this.setupSourceBuffer_();
- _this.setupFirstPlay();
- _this.useAudio();
- });
- this.masterPlaylistLoader_.on('loadedplaylist', function () {
- var updatedPlaylist = _this.masterPlaylistLoader_.media();
- var seekable = undefined;
- if (!updatedPlaylist) {
- // select the initial variant
- _this.initialMedia_ = _this.selectPlaylist();
- _this.masterPlaylistLoader_.media(_this.initialMedia_);
- _this.fillAudioTracks_();
- _this.trigger('selectedinitialmedia');
- return;
- }
- _this.mainSegmentLoader_.playlist(updatedPlaylist);
- _this.mainSegmentLoader_.expired(_this.masterPlaylistLoader_.expired_);
- _this.updateDuration();
- // update seekable
- seekable = _this.seekable();
- if (!updatedPlaylist.endList && seekable.length !== 0) {
- _this.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0));
- }
- });
- this.masterPlaylistLoader_.on('error', function () {
- _this.blacklistCurrentPlaylist(_this.masterPlaylistLoader_.error);
- });
- this.masterPlaylistLoader_.on('mediachanging', function () {
- _this.mainSegmentLoader_.pause();
- });
- this.masterPlaylistLoader_.on('mediachange', function () {
- _this.mainSegmentLoader_.abort();
- _this.mainSegmentLoader_.load();
- _this.tech_.trigger({
- type: 'mediachange',
- bubbles: true
- });
- });
- this.mainSegmentLoader_.on('progress', function () {
- // figure out what stream the next segment should be downloaded from
- // with the updated bandwidth information
- _this.masterPlaylistLoader_.media(_this.selectPlaylist());
- _this.trigger('progress');
- });
- this.mainSegmentLoader_.on('error', function () {
- _this.blacklistCurrentPlaylist(_this.mainSegmentLoader_.error());
- });
- this.audioSegmentLoader_.on('error', function () {
- _videoJs2['default'].log.warn('Problem encountered with the current alternate audio track' + '. Switching back to default.');
- _this.audioSegmentLoader_.abort();
- _this.audioPlaylistLoader_ = null;
- _this.useAudio();
- });
- this.masterPlaylistLoader_.load();
- }
- /**
- * fill our internal list of HlsAudioTracks with data from
- * the master playlist or use a default
- *
- * @private
- */
- _createClass(MasterPlaylistController, [{
- key: 'fillAudioTracks_',
- value: function fillAudioTracks_() {
- var master = this.master();
- var mediaGroups = master.mediaGroups || {};
- // force a default if we have none or we are not
- // in html5 mode (the only mode to support more than one
- // audio track)
- if (!mediaGroups || !mediaGroups.AUDIO || Object.keys(mediaGroups.AUDIO).length === 0 || this.mode_ !== 'html5') {
- // "main" audio group, track name "default"
- mediaGroups.AUDIO = { main: { 'default': { 'default': true } } };
- }
- var tracks = {};
- for (var mediaGroup in mediaGroups.AUDIO) {
- for (var label in mediaGroups.AUDIO[mediaGroup]) {
- var properties = mediaGroups.AUDIO[mediaGroup][label];
- // if the track already exists add a new "location"
- // since tracks in different mediaGroups are actually the same
- // track with different locations to download them from
- if (tracks[label]) {
- tracks[label].addLoader(mediaGroup, properties.resolvedUri);
- continue;
- }
- var track = new _hlsAudioTrack2['default'](_videoJs2['default'].mergeOptions(properties, {
- hls: this.hls_,
- withCredentials: this.withCredential,
- mediaGroup: mediaGroup,
- label: label
- }));
- tracks[label] = track;
- this.audioTracks_.push(track);
- }
- }
- }
- /**
- * Call load on our SegmentLoaders
- */
- }, {
- key: 'load',
- value: function load() {
- this.mainSegmentLoader_.load();
- if (this.audioPlaylistLoader_) {
- this.audioSegmentLoader_.load();
- }
- }
- /**
- * Get the current active Media Group for Audio
- * given the selected playlist and its attributes
- */
- }, {
- key: 'activeAudioGroup',
- value: function activeAudioGroup() {
- var media = this.masterPlaylistLoader_.media();
- var mediaGroup = 'main';
- if (media && media.attributes && media.attributes.AUDIO) {
- mediaGroup = media.attributes.AUDIO;
- }
- return mediaGroup;
- }
- /**
- * Use any audio track that we have, and start to load it
- */
- }, {
- key: 'useAudio',
- value: function useAudio() {
- var _this2 = this;
- var track = undefined;
- this.audioTracks_.forEach(function (t) {
- if (!track && t.enabled) {
- track = t;
- }
- });
- // called too early or no track is enabled
- if (!track) {
- return;
- }
- // Pause any alternative audio
- if (this.audioPlaylistLoader_) {
- this.audioPlaylistLoader_.pause();
- this.audioPlaylistLoader_ = null;
- this.audioSegmentLoader_.pause();
- }
- // If the audio track for the active audio group has
- // a playlist loader than it is an alterative audio track
- // otherwise it is a part of the mainSegmenLoader
- var loader = track.getLoader(this.activeAudioGroup());
- if (!loader) {
- this.mainSegmentLoader_.clearBuffer();
- return;
- }
- // TODO: it may be better to create the playlist loader here
- // when we can change an audioPlaylistLoaders src
- this.audioPlaylistLoader_ = loader;
- if (this.audioPlaylistLoader_.started) {
- this.audioPlaylistLoader_.load();
- this.audioSegmentLoader_.load();
- this.audioSegmentLoader_.clearBuffer();
- return;
- }
- this.audioPlaylistLoader_.on('loadedmetadata', function () {
- /* eslint-disable no-shadow */
- var media = _this2.audioPlaylistLoader_.media();
- /* eslint-enable no-shadow */
- _this2.audioSegmentLoader_.playlist(media);
- _this2.addMimeType_(_this2.audioSegmentLoader_, 'mp4a.40.2', media);
- // if the video is already playing, or if this isn't a live video and preload
- // permits, start downloading segments
- if (!_this2.tech_.paused() || media.endList && _this2.tech_.preload() !== 'none') {
- _this2.audioSegmentLoader_.load();
- }
- if (!media.endList) {
- // trigger the playlist loader to start "expired time"-tracking
- _this2.audioPlaylistLoader_.trigger('firstplay');
- }
- });
- this.audioPlaylistLoader_.on('loadedplaylist', function () {
- var updatedPlaylist = undefined;
- if (_this2.audioPlaylistLoader_) {
- updatedPlaylist = _this2.audioPlaylistLoader_.media();
- }
- if (!updatedPlaylist) {
- // only one playlist to select
- _this2.audioPlaylistLoader_.media(_this2.audioPlaylistLoader_.playlists.master.playlists[0]);
- return;
- }
- _this2.audioSegmentLoader_.playlist(updatedPlaylist);
- });
- this.audioPlaylistLoader_.on('error', function () {
- _videoJs2['default'].log.warn('Problem encountered loading the alternate audio track' + '. Switching back to default.');
- _this2.audioSegmentLoader_.abort();
- _this2.audioPlaylistLoader_ = null;
- _this2.useAudio();
- });
- this.audioSegmentLoader_.clearBuffer();
- this.audioPlaylistLoader_.start();
- }
- /**
- * Re-tune playback quality level for the current player
- * conditions. This method may perform destructive actions, like
- * removing already buffered content, to readjust the currently
- * active playlist quickly.
- *
- * @private
- */
- }, {
- key: 'fastQualityChange_',
- value: function fastQualityChange_() {
- var media = this.selectPlaylist();
- if (media !== this.masterPlaylistLoader_.media()) {
- this.masterPlaylistLoader_.media(media);
- this.mainSegmentLoader_.sourceUpdater_.remove(this.tech_.currentTime() + 5, Infinity);
- }
- }
- /**
- * Begin playback.
- */
- }, {
- key: 'play',
- value: function play() {
- if (this.setupFirstPlay()) {
- return;
- }
- if (this.tech_.ended()) {
- this.tech_.setCurrentTime(0);
- }
- this.load();
- // if the viewer has paused and we fell out of the live window,
- // seek forward to the earliest available position
- if (this.tech_.duration() === Infinity) {
- if (this.tech_.currentTime() < this.tech_.seekable().start(0)) {
- return this.tech_.setCurrentTime(this.tech_.seekable().start(0));
- }
- }
- }
- /**
- * Seek to the latest media position if this is a live video and the
- * player and video are loaded and initialized.
- */
- }, {
- key: 'setupFirstPlay',
- value: function setupFirstPlay() {
- var seekable = undefined;
- var media = this.masterPlaylistLoader_.media();
- // check that everything is ready to begin buffering
- // 1) the active media playlist is available
- if (media &&
- // 2) the video is a live stream
- !media.endList &&
- // 3) the player is not paused
- !this.tech_.paused() &&
- // 4) the player has not started playing
- !this.hasPlayed_) {
- this.load();
- // trigger the playlist loader to start "expired time"-tracking
- this.masterPlaylistLoader_.trigger('firstplay');
- this.hasPlayed_ = true;
- // seek to the latest media position for live videos
- seekable = this.seekable();
- if (seekable.length) {
- this.tech_.setCurrentTime(seekable.end(0));
- }
- return true;
- }
- return false;
- }
- /**
- * handle the sourceopen event on the MediaSource
- *
- * @private
- */
- }, {
- key: 'handleSourceOpen_',
- value: function handleSourceOpen_() {
- // Only attempt to create the source buffer if none already exist.
- // handleSourceOpen is also called when we are "re-opening" a source buffer
- // after `endOfStream` has been called (in response to a seek for instance)
- this.setupSourceBuffer_();
- // if autoplay is enabled, begin playback. This is duplicative of
- // code in video.js but is required because play() must be invoked
- // *after* the media source has opened.
- if (this.tech_.autoplay()) {
- this.tech_.play();
- }
- this.trigger('sourceopen');
- }
- /**
- * Blacklists a playlist when an error occurs for a set amount of time
- * making it unavailable for selection by the rendition selection algorithm
- * and then forces a new playlist (rendition) selection.
- *
- * @param {Object=} error an optional error that may include the playlist
- * to blacklist
- */
- }, {
- key: 'blacklistCurrentPlaylist',
- value: function blacklistCurrentPlaylist() {
- var error = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
- var currentPlaylist = undefined;
- var nextPlaylist = undefined;
- // If the `error` was generated by the playlist loader, it will contain
- // the playlist we were trying to load (but failed) and that should be
- // blacklisted instead of the currently selected playlist which is likely
- // out-of-date in this scenario
- currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
- // If there is no current playlist, then an error occurred while we were
- // trying to load the master OR while we were disposing of the tech
- if (!currentPlaylist) {
- this.error = error;
- return this.mediaSource.endOfStream('network');
- }
- // Blacklist this playlist
- currentPlaylist.excludeUntil = Date.now() + BLACKLIST_DURATION;
- // Select a new playlist
- nextPlaylist = this.selectPlaylist();
- if (nextPlaylist) {
- _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. Switching to another playlist.');
- return this.masterPlaylistLoader_.media(nextPlaylist);
- }
- _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. No suitable alternatives found.');
- // We have no more playlists we can select so we must fail
- this.error = error;
- return this.mediaSource.endOfStream('network');
- }
- /**
- * Pause all segment loaders
- */
- }, {
- key: 'pauseLoading',
- value: function pauseLoading() {
- this.mainSegmentLoader_.pause();
- if (this.audioPlaylistLoader_) {
- this.audioSegmentLoader_.pause();
- }
- }
- /**
- * set the current time on all segment loaders
- *
- * @param {TimeRange} currentTime the current time to set
- * @return {TimeRange} the current time
- */
- }, {
- key: 'setCurrentTime',
- value: function setCurrentTime(currentTime) {
- var buffered = _ranges2['default'].findRange(this.tech_.buffered(), currentTime);
- if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
- // return immediately if the metadata is not ready yet
- return 0;
- }
- // it's clearly an edge-case but don't thrown an error if asked to
- // seek within an empty playlist
- if (!this.masterPlaylistLoader_.media().segments) {
- return 0;
- }
- // if the seek location is already buffered, continue buffering as
- // usual
- if (buffered && buffered.length) {
- return currentTime;
- }
- // cancel outstanding requests so we begin buffering at the new
- // location
- this.mainSegmentLoader_.abort();
- if (this.audioPlaylistLoader_) {
- this.audioSegmentLoader_.abort();
- }
- if (!this.tech_.paused()) {
- this.mainSegmentLoader_.load();
- if (this.audioPlaylistLoader_) {
- this.audioSegmentLoader_.load();
- }
- }
- }
- /**
- * get the current duration
- *
- * @return {TimeRange} the duration
- */
- }, {
- key: 'duration',
- value: function duration() {
- if (!this.masterPlaylistLoader_) {
- return 0;
- }
- if (this.mediaSource) {
- return this.mediaSource.duration;
- }
- return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
- }
- /**
- * check the seekable range
- *
- * @return {TimeRange} the seekable range
- */
- }, {
- key: 'seekable',
- value: function seekable() {
- var media = undefined;
- var mainSeekable = undefined;
- var audioSeekable = undefined;
- if (!this.masterPlaylistLoader_) {
- return _videoJs2['default'].createTimeRanges();
- }
- media = this.masterPlaylistLoader_.media();
- if (!media) {
- return _videoJs2['default'].createTimeRanges();
- }
- mainSeekable = Hls.Playlist.seekable(media, this.masterPlaylistLoader_.expired_);
- if (mainSeekable.length === 0) {
- return mainSeekable;
- }
- if (this.audioPlaylistLoader_) {
- audioSeekable = Hls.Playlist.seekable(this.audioPlaylistLoader_.media(), this.audioPlaylistLoader_.expired_);
- if (audioSeekable.length === 0) {
- return audioSeekable;
- }
- }
- if (!audioSeekable) {
- // seekable has been calculated based on buffering video data so it
- // can be returned directly
- return mainSeekable;
- }
- return _videoJs2['default'].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)]]);
- }
- /**
- * Update the player duration
- */
- }, {
- key: 'updateDuration',
- value: function updateDuration() {
- var _this3 = this;
- var oldDuration = this.mediaSource.duration;
- var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
- var buffered = this.tech_.buffered();
- var setDuration = function setDuration() {
- _this3.mediaSource.duration = newDuration;
- _this3.tech_.trigger('durationchange');
- _this3.mediaSource.removeEventListener('sourceopen', setDuration);
- };
- if (buffered.length > 0) {
- newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
- }
- // if the duration has changed, invalidate the cached value
- if (oldDuration !== newDuration) {
- // update the duration
- if (this.mediaSource.readyState !== 'open') {
- this.mediaSource.addEventListener('sourceopen', setDuration);
- } else {
- setDuration();
- }
- }
- }
- /**
- * dispose of the MasterPlaylistController and everything
- * that it controls
- */
- }, {
- key: 'dispose',
- value: function dispose() {
- this.masterPlaylistLoader_.dispose();
- this.audioTracks_.forEach(function (track) {
- track.dispose();
- });
- this.audioTracks_.length = 0;
- this.mainSegmentLoader_.dispose();
- this.audioSegmentLoader_.dispose();
- }
- /**
- * return the master playlist object if we have one
- *
- * @return {Object} the master playlist object that we parsed
- */
- }, {
- key: 'master',
- value: function master() {
- return this.masterPlaylistLoader_.master;
- }
- /**
- * return the currently selected playlist
- *
- * @return {Object} the currently selected playlist object that we parsed
- */
- }, {
- key: 'media',
- value: function media() {
- // playlist loader will not return media if it has not been fully loaded
- return this.masterPlaylistLoader_.media() || this.initialMedia_;
- }
- /**
- * setup our internal source buffers on our segment Loaders
- *
- * @private
- */
- }, {
- key: 'setupSourceBuffer_',
- value: function setupSourceBuffer_() {
- var media = this.masterPlaylistLoader_.media();
- // wait until a media playlist is available and the Media Source is
- // attached
- if (!media || this.mediaSource.readyState !== 'open') {
- return;
- }
- this.addMimeType_(this.mainSegmentLoader_, 'avc1.4d400d, mp4a.40.2', media);
- // exclude any incompatible variant streams from future playlist
- // selection
- this.excludeIncompatibleVariants_(media);
- }
- /**
- * add a time type to a segmentLoader
- *
- * @param {SegmentLoader} segmentLoader the segmentloader to work on
- * @param {String} codecs to use by default
- * @param {Object} the parsed media object
- * @private
- */
- }, {
- key: 'addMimeType_',
- value: function addMimeType_(segmentLoader, defaultCodecs, media) {
- var mimeType = 'video/mp2t';
- // if the codecs were explicitly specified, pass them along to the
- // source buffer
- if (media.attributes && media.attributes.CODECS) {
- mimeType += '; codecs="' + media.attributes.CODECS + '"';
- } else {
- mimeType += '; codecs="' + defaultCodecs + '"';
- }
- segmentLoader.mimeType(mimeType);
- }
- /**
- * Blacklist playlists that are known to be codec or
- * stream-incompatible with the SourceBuffer configuration. For
- * instance, Media Source Extensions would cause the video element to
- * stall waiting for video data if you switched from a variant with
- * video and audio to an audio-only one.
- *
- * @param {Object} media a media playlist compatible with the current
- * set of SourceBuffers. Variants in the current master playlist that
- * do not appear to have compatible codec or stream configurations
- * will be excluded from the default playlist selection algorithm
- * indefinitely.
- * @private
- */
- }, {
- key: 'excludeIncompatibleVariants_',
- value: function excludeIncompatibleVariants_(media) {
- var master = this.masterPlaylistLoader_.master;
- var codecCount = 2;
- var videoCodec = null;
- var audioProfile = null;
- var codecs = undefined;
- if (media.attributes && media.attributes.CODECS) {
- codecs = parseCodecs(media.attributes.CODECS);
- videoCodec = codecs.videoCodec;
- audioProfile = codecs.audioProfile;
- codecCount = codecs.codecCount;
- }
- master.playlists.forEach(function (variant) {
- var variantCodecs = {
- codecCount: 2,
- videoCodec: null,
- audioProfile: null
- };
- if (variant.attributes && variant.attributes.CODECS) {
- variantCodecs = parseCodecs(variant.attributes.CODECS);
- }
- // if the streams differ in the presence or absence of audio or
- // video, they are incompatible
- if (variantCodecs.codecCount !== codecCount) {
- variant.excludeUntil = Infinity;
- }
- // if h.264 is specified on the current playlist, some flavor of
- // it must be specified on all compatible variants
- if (variantCodecs.videoCodec !== videoCodec) {
- variant.excludeUntil = Infinity;
- }
- // HE-AAC ("mp4a.40.5") is incompatible with all other versions of
- // AAC audio in Chrome 46. Don't mix the two.
- if (variantCodecs.audioProfile === '5' && audioProfile !== '5' || audioProfile === '5' && variantCodecs.audioProfile !== '5') {
- variant.excludeUntil = Infinity;
- }
- });
- }
- }]);
- return MasterPlaylistController;
- })(_videoJs2['default'].EventTarget);
- exports['default'] = MasterPlaylistController;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./hls-audio-track":6,"./playlist-loader":12,"./ranges":14,"./segment-loader":16}],12:[function(require,module,exports){
- (function (global){
- /**
- * @file playlist-loader.js
- *
- * A state machine that manages the loading, caching, and updating of
- * M3U8 playlists.
- *
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _resolveUrl = require('./resolve-url');
- var _resolveUrl2 = _interopRequireDefault(_resolveUrl);
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _stream = require('./stream');
- var _stream2 = _interopRequireDefault(_stream);
- var _m3u8 = require('./m3u8');
- var _m3u82 = _interopRequireDefault(_m3u8);
- /**
- * Returns a new array of segments that is the result of merging
- * properties from an older list of segments onto an updated
- * list. No properties on the updated playlist will be overridden.
- *
- * @param {Array} original the outdated list of segments
- * @param {Array} update the updated list of segments
- * @param {Number=} offset the index of the first update
- * segment in the original segment list. For non-live playlists,
- * this should always be zero and does not need to be
- * specified. For live playlists, it should be the difference
- * between the media sequence numbers in the original and updated
- * playlists.
- * @return a list of merged segment objects
- */
- var updateSegments = function updateSegments(original, update, offset) {
- var result = update.slice();
- var length = undefined;
- var i = undefined;
- offset = offset || 0;
- length = Math.min(original.length, update.length + offset);
- for (i = offset; i < length; i++) {
- result[i - offset] = (0, _videoJs.mergeOptions)(original[i], result[i - offset]);
- }
- return result;
- };
- /**
- * Returns a new master playlist that is the result of merging an
- * updated media playlist into the original version. If the
- * updated media playlist does not match any of the playlist
- * entries in the original master playlist, null is returned.
- *
- * @param {Object} master a parsed master M3U8 object
- * @param {Object} media a parsed media M3U8 object
- * @return {Object} a new object that represents the original
- * master playlist with the updated media playlist merged in, or
- * null if the merge produced no change.
- */
- var updateMaster = function updateMaster(master, media) {
- var changed = false;
- var result = (0, _videoJs.mergeOptions)(master, {});
- var i = master.playlists.length;
- var playlist = undefined;
- var segment = undefined;
- var j = undefined;
- while (i--) {
- playlist = result.playlists[i];
- if (playlist.uri === media.uri) {
- // consider the playlist unchanged if the number of segments
- // are equal and the media sequence number is unchanged
- if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) {
- continue;
- }
- result.playlists[i] = (0, _videoJs.mergeOptions)(playlist, media);
- result.playlists[media.uri] = result.playlists[i];
- // if the update could overlap existing segment information,
- // merge the two lists
- if (playlist.segments) {
- result.playlists[i].segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
- }
- // resolve any missing segment and key URIs
- j = 0;
- if (result.playlists[i].segments) {
- j = result.playlists[i].segments.length;
- }
- while (j--) {
- segment = result.playlists[i].segments[j];
- if (!segment.resolvedUri) {
- segment.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.uri);
- }
- if (segment.key && !segment.key.resolvedUri) {
- segment.key.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.key.uri);
- }
- }
- changed = true;
- }
- }
- return changed ? result : null;
- };
- /**
- * Load a playlist from a remote loacation
- *
- * @class PlaylistLoader
- * @extends Stream
- * @param {String} srcUrl the url to start with
- * @param {Boolean} withCredentials the withCredentials xhr option
- * @constructor
- */
- var PlaylistLoader = function PlaylistLoader(srcUrl, hls, withCredentials) {
- var _this = this;
- /* eslint-disable consistent-this */
- var loader = this;
- /* eslint-enable consistent-this */
- var dispose = undefined;
- var mediaUpdateTimeout = undefined;
- var request = undefined;
- var playlistRequestError = undefined;
- var haveMetadata = undefined;
- PlaylistLoader.prototype.constructor.call(this);
- this.hls_ = hls;
- // a flag that disables "expired time"-tracking this setting has
- // no effect when not playing a live stream
- this.trackExpiredTime_ = false;
- if (!srcUrl) {
- throw new Error('A non-empty playlist URL is required');
- }
- playlistRequestError = function (xhr, url, startingState) {
- loader.setBandwidth(request || xhr);
- // any in-flight request is now finished
- request = null;
- if (startingState) {
- loader.state = startingState;
- }
- loader.error = {
- playlist: loader.master.playlists[url],
- status: xhr.status,
- message: 'HLS playlist request error at URL: ' + url,
- responseText: xhr.responseText,
- code: xhr.status >= 500 ? 4 : 2
- };
- loader.trigger('error');
- };
- // update the playlist loader's state in response to a new or
- // updated playlist.
- haveMetadata = function (xhr, url) {
- var parser = undefined;
- var refreshDelay = undefined;
- var update = undefined;
- loader.setBandwidth(request || xhr);
- // any in-flight request is now finished
- request = null;
- loader.state = 'HAVE_METADATA';
- parser = new _m3u82['default'].Parser();
- parser.push(xhr.responseText);
- parser.end();
- parser.manifest.uri = url;
- // merge this playlist into the master
- update = updateMaster(loader.master, parser.manifest);
- refreshDelay = (parser.manifest.targetDuration || 10) * 1000;
- if (update) {
- loader.master = update;
- loader.updateMediaPlaylist_(parser.manifest);
- } else {
- // if the playlist is unchanged since the last reload,
- // try again after half the target duration
- refreshDelay /= 2;
- }
- // refresh live playlists after a target duration passes
- if (!loader.media().endList) {
- window.clearTimeout(mediaUpdateTimeout);
- mediaUpdateTimeout = window.setTimeout(function () {
- loader.trigger('mediaupdatetimeout');
- }, refreshDelay);
- }
- loader.trigger('loadedplaylist');
- };
- // initialize the loader state
- loader.state = 'HAVE_NOTHING';
- // track the time that has expired from the live window
- // this allows the seekable start range to be calculated even if
- // all segments with timing information have expired
- this.expired_ = 0;
- // capture the prototype dispose function
- dispose = this.dispose;
- /**
- * Abort any outstanding work and clean up.
- */
- loader.dispose = function () {
- loader.stopRequest();
- window.clearTimeout(mediaUpdateTimeout);
- dispose.call(this);
- };
- loader.stopRequest = function () {
- if (request) {
- var oldRequest = request;
- request = null;
- oldRequest.onreadystatechange = null;
- oldRequest.abort();
- }
- };
- /**
- * When called without any arguments, returns the currently
- * active media playlist. When called with a single argument,
- * triggers the playlist loader to asynchronously switch to the
- * specified media playlist. Calling this method while the
- * loader is in the HAVE_NOTHING causes an error to be emitted
- * but otherwise has no effect.
- *
- * @param {Object=} playlis tthe parsed media playlist
- * object to switch to
- * @return {Playlist} the current loaded media
- */
- loader.media = function (playlist) {
- var startingState = loader.state;
- var mediaChange = undefined;
- // getter
- if (!playlist) {
- return loader.media_;
- }
- // setter
- if (loader.state === 'HAVE_NOTHING') {
- throw new Error('Cannot switch media playlist from ' + loader.state);
- }
- // find the playlist object if the target playlist has been
- // specified by URI
- if (typeof playlist === 'string') {
- if (!loader.master.playlists[playlist]) {
- throw new Error('Unknown playlist URI: ' + playlist);
- }
- playlist = loader.master.playlists[playlist];
- }
- mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri;
- // switch to fully loaded playlists immediately
- if (loader.master.playlists[playlist.uri].endList) {
- // abort outstanding playlist requests
- if (request) {
- request.onreadystatechange = null;
- request.abort();
- request = null;
- }
- loader.state = 'HAVE_METADATA';
- loader.media_ = playlist;
- // trigger media change if the active media has been updated
- if (mediaChange) {
- loader.trigger('mediachanging');
- loader.trigger('mediachange');
- }
- return;
- }
- // switching to the active playlist is a no-op
- if (!mediaChange) {
- return;
- }
- loader.state = 'SWITCHING_MEDIA';
- // there is already an outstanding playlist request
- if (request) {
- if ((0, _resolveUrl2['default'])(loader.master.uri, playlist.uri) === request.url) {
- // requesting to switch to the same playlist multiple times
- // has no effect after the first
- return;
- }
- request.onreadystatechange = null;
- request.abort();
- request = null;
- }
- // request the new playlist
- if (this.media_) {
- this.trigger('mediachanging');
- }
- request = this.hls_.xhr({
- uri: (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri),
- withCredentials: withCredentials
- }, function (error, req) {
- // disposed
- if (!request) {
- return;
- }
- if (error) {
- return playlistRequestError(request, playlist.uri, startingState);
- }
- haveMetadata(req, playlist.uri);
- // fire loadedmetadata the first time a media playlist is loaded
- if (startingState === 'HAVE_MASTER') {
- loader.trigger('loadedmetadata');
- } else {
- loader.trigger('mediachange');
- }
- });
- };
- /**
- * set the bandwidth on an xhr to the bandwidth on the playlist
- */
- loader.setBandwidth = function (xhr) {
- loader.bandwidth = xhr.bandwidth;
- };
- // In a live playlist, don't keep track of the expired time
- // until HLS tells us that "first play" has commenced
- loader.on('firstplay', function () {
- this.trackExpiredTime_ = true;
- });
- // live playlist staleness timeout
- loader.on('mediaupdatetimeout', function () {
- if (loader.state !== 'HAVE_METADATA') {
- // only refresh the media playlist if no other activity is going on
- return;
- }
- loader.state = 'HAVE_CURRENT_METADATA';
- request = this.hls_.xhr({
- uri: (0, _resolveUrl2['default'])(loader.master.uri, loader.media().uri),
- withCredentials: withCredentials
- }, function (error, req) {
- // disposed
- if (!request) {
- return;
- }
- if (error) {
- return playlistRequestError(request, loader.media().uri);
- }
- haveMetadata(request, loader.media().uri);
- });
- });
- /**
- * pause loading of the playlist
- */
- loader.pause = function () {
- loader.stopRequest();
- window.clearTimeout(mediaUpdateTimeout);
- };
- /**
- * start loading of the playlist
- */
- loader.load = function () {
- if (loader.started) {
- if (!loader.media().endList) {
- loader.trigger('mediaupdatetimeout');
- } else {
- loader.trigger('loadedplaylist');
- }
- } else {
- loader.start();
- }
- };
- /**
- * start loading of the playlist
- */
- loader.start = function () {
- loader.started = true;
- // request the specified URL
- request = _this.hls_.xhr({
- uri: srcUrl,
- withCredentials: withCredentials
- }, function (error, req) {
- var parser = undefined;
- var playlist = undefined;
- var i = undefined;
- // disposed
- if (!request) {
- return;
- }
- // clear the loader's request reference
- request = null;
- if (error) {
- loader.error = {
- status: req.status,
- message: 'HLS playlist request error at URL: ' + srcUrl,
- responseText: req.responseText,
- // MEDIA_ERR_NETWORK
- code: 2
- };
- return loader.trigger('error');
- }
- parser = new _m3u82['default'].Parser();
- parser.push(req.responseText);
- parser.end();
- loader.state = 'HAVE_MASTER';
- parser.manifest.uri = srcUrl;
- // loaded a master playlist
- if (parser.manifest.playlists) {
- loader.master = parser.manifest;
- // setup by-URI lookups and resolve media playlist URIs
- i = loader.master.playlists.length;
- while (i--) {
- playlist = loader.master.playlists[i];
- loader.master.playlists[playlist.uri] = playlist;
- playlist.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri);
- }
- // resolve any media group URIs
- for (var groupKey in loader.master.mediaGroups.AUDIO) {
- for (var labelKey in loader.master.mediaGroups.AUDIO[groupKey]) {
- var alternateAudio = loader.master.mediaGroups.AUDIO[groupKey][labelKey];
- if (alternateAudio.uri) {
- alternateAudio.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, alternateAudio.uri);
- }
- }
- }
- loader.trigger('loadedplaylist');
- if (!request) {
- // no media playlist was specifically selected so start
- // from the first listed one
- loader.media(parser.manifest.playlists[0]);
- }
- return;
- }
- // loaded a media playlist
- // infer a master playlist if none was previously requested
- loader.master = {
- uri: window.location.href,
- playlists: [{
- uri: srcUrl
- }]
- };
- loader.master.playlists[srcUrl] = loader.master.playlists[0];
- loader.master.playlists[0].resolvedUri = srcUrl;
- haveMetadata(req, srcUrl);
- return loader.trigger('loadedmetadata');
- });
- };
- };
- PlaylistLoader.prototype = new _stream2['default']();
- /**
- * Update the PlaylistLoader state to reflect the changes in an
- * update to the current media playlist.
- *
- * @param {Object} update the updated media playlist object
- */
- PlaylistLoader.prototype.updateMediaPlaylist_ = function (update) {
- var outdated = undefined;
- var i = undefined;
- var segment = undefined;
- outdated = this.media_;
- this.media_ = this.master.playlists[update.uri];
- if (!outdated) {
- return;
- }
- // don't track expired time until this flag is truthy
- if (!this.trackExpiredTime_) {
- return;
- }
- // if the update was the result of a rendition switch do not
- // attempt to calculate expired_ since media-sequences need not
- // correlate between renditions/variants
- if (update.uri !== outdated.uri) {
- return;
- }
- // try using precise timing from first segment of the updated
- // playlist
- if (update.segments.length) {
- if (typeof update.segments[0].start !== 'undefined') {
- this.expired_ = update.segments[0].start;
- return;
- } else if (typeof update.segments[0].end !== 'undefined') {
- this.expired_ = update.segments[0].end - update.segments[0].duration;
- return;
- }
- }
- // calculate expired by walking the outdated playlist
- i = update.mediaSequence - outdated.mediaSequence - 1;
- for (; i >= 0; i--) {
- segment = outdated.segments[i];
- if (!segment) {
- // we missed information on this segment completely between
- // playlist updates so we'll have to take an educated guess
- // once we begin buffering again, any error we introduce can
- // be corrected
- this.expired_ += outdated.targetDuration || 10;
- continue;
- }
- if (typeof segment.end !== 'undefined') {
- this.expired_ = segment.end;
- return;
- }
- if (typeof segment.start !== 'undefined') {
- this.expired_ = segment.start + segment.duration;
- return;
- }
- this.expired_ += segment.duration;
- }
- };
- exports['default'] = PlaylistLoader;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./m3u8":7,"./resolve-url":15,"./stream":18}],13:[function(require,module,exports){
- (function (global){
- /**
- * @file playlist.js
- *
- * Playlist related utilities.
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var Playlist = {
- /**
- * The number of segments that are unsafe to start playback at in
- * a live stream. Changing this value can cause playback stalls.
- * See HTTP Live Streaming, "Playing the Media Playlist File"
- * https://tools.ietf.org/html/draft-pantos-http-live-streaming-18#section-6.3.3
- */
- UNSAFE_LIVE_SEGMENTS: 3
- };
- /**
- * walk backward until we find a duration we can use
- * or return a failure
- *
- * @param {Playlist} playlist the playlist to walk through
- * @param {Number} endSequence the mediaSequence to stop walking on
- */
- var backwardDuration = function backwardDuration(playlist, endSequence) {
- var result = 0;
- var i = endSequence - playlist.mediaSequence;
- // if a start time is available for segment immediately following
- // the interval, use it
- var segment = playlist.segments[i];
- // Walk backward until we find the latest segment with timeline
- // information that is earlier than endSequence
- if (segment) {
- if (typeof segment.start !== 'undefined') {
- return { result: segment.start, precise: true };
- }
- if (typeof segment.end !== 'undefined') {
- return {
- result: segment.end - segment.duration,
- precise: true
- };
- }
- }
- while (i--) {
- segment = playlist.segments[i];
- if (typeof segment.end !== 'undefined') {
- return { result: result + segment.end, precise: true };
- }
- result += segment.duration;
- if (typeof segment.start !== 'undefined') {
- return { result: result + segment.start, precise: true };
- }
- }
- return { result: result, precise: false };
- };
- /**
- * walk forward until we find a duration we can use
- * or return a failure
- *
- * @param {Playlist} playlist the playlist to walk through
- * @param {Number} endSequence the mediaSequence to stop walking on
- */
- var forwardDuration = function forwardDuration(playlist, endSequence) {
- var result = 0;
- var segment = undefined;
- var i = endSequence - playlist.mediaSequence;
- // Walk forward until we find the earliest segment with timeline
- // information
- for (; i < playlist.segments.length; i++) {
- segment = playlist.segments[i];
- if (typeof segment.start !== 'undefined') {
- return {
- result: segment.start - result,
- precise: true
- };
- }
- result += segment.duration;
- if (typeof segment.end !== 'undefined') {
- return {
- result: segment.end - result,
- precise: true
- };
- }
- }
- // indicate we didn't find a useful duration estimate
- return { result: -1, precise: false };
- };
- /**
- * Calculate the media duration from the segments associated with a
- * playlist. The duration of a subinterval of the available segments
- * may be calculated by specifying an end index.
- *
- * @param {Object} playlist a media playlist object
- * @param {Number=} endSequence an exclusive upper boundary
- * for the playlist. Defaults to playlist length.
- * @param {Number} expired the amount of time that has dropped
- * off the front of the playlist in a live scenario
- * @return {Number} the duration between the first available segment
- * and end index.
- */
- var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
- var backward = undefined;
- var forward = undefined;
- if (typeof endSequence === 'undefined') {
- endSequence = playlist.mediaSequence + playlist.segments.length;
- }
- if (endSequence < playlist.mediaSequence) {
- return 0;
- }
- // do a backward walk to estimate the duration
- backward = backwardDuration(playlist, endSequence);
- if (backward.precise) {
- // if we were able to base our duration estimate on timing
- // information provided directly from the Media Source, return
- // it
- return backward.result;
- }
- // walk forward to see if a precise duration estimate can be made
- // that way
- forward = forwardDuration(playlist, endSequence);
- if (forward.precise) {
- // we found a segment that has been buffered and so it's
- // position is known precisely
- return forward.result;
- }
- // return the less-precise, playlist-based duration estimate
- return backward.result + expired;
- };
- /**
- * Calculates the duration of a playlist. If a start and end index
- * are specified, the duration will be for the subset of the media
- * timeline between those two indices. The total duration for live
- * playlists is always Infinity.
- *
- * @param {Object} playlist a media playlist object
- * @param {Number=} endSequence an exclusive upper
- * boundary for the playlist. Defaults to the playlist media
- * sequence number plus its length.
- * @param {Number=} expired the amount of time that has
- * dropped off the front of the playlist in a live scenario
- * @return {Number} the duration between the start index and end
- * index.
- */
- var duration = function duration(playlist, endSequence, expired) {
- if (!playlist) {
- return 0;
- }
- if (typeof expired !== 'number') {
- expired = 0;
- }
- // if a slice of the total duration is not requested, use
- // playlist-level duration indicators when they're present
- if (typeof endSequence === 'undefined') {
- // if present, use the duration specified in the playlist
- if (playlist.totalDuration) {
- return playlist.totalDuration;
- }
- // duration should be Infinity for live playlists
- if (!playlist.endList) {
- return window.Infinity;
- }
- }
- // calculate the total duration based on the segment durations
- return intervalDuration(playlist, endSequence, expired);
- };
- exports.duration = duration;
- /**
- * Calculates the interval of time that is currently seekable in a
- * playlist. The returned time ranges are relative to the earliest
- * moment in the specified playlist that is still available. A full
- * seekable implementation for live streams would need to offset
- * these values by the duration of content that has expired from the
- * stream.
- *
- * @param {Object} playlist a media playlist object
- * @param {Number=} expired the amount of time that has
- * dropped off the front of the playlist in a live scenario
- * @return {TimeRanges} the periods of time that are valid targets
- * for seeking
- */
- var seekable = function seekable(playlist, expired) {
- var start = undefined;
- var end = undefined;
- var endSequence = undefined;
- if (typeof expired !== 'number') {
- expired = 0;
- }
- // without segments, there are no seekable ranges
- if (!playlist || !playlist.segments) {
- return (0, _videoJs.createTimeRange)();
- }
- // when the playlist is complete, the entire duration is seekable
- if (playlist.endList) {
- return (0, _videoJs.createTimeRange)(0, duration(playlist));
- }
- // live playlists should not expose three segment durations worth
- // of content from the end of the playlist
- // https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3
- start = intervalDuration(playlist, playlist.mediaSequence, expired);
- endSequence = Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS);
- end = intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
- return (0, _videoJs.createTimeRange)(start, end);
- };
- exports.seekable = seekable;
- /**
- * Determine the index of the segment that contains a specified
- * playback position in a media playlist.
- *
- * @param {Object} playlist the media playlist to query
- * @param {Number} time The number of seconds since the earliest
- * possible position to determine the containing segment for
- * @param {Number=} expired the duration of content, in
- * seconds, that has been removed from this playlist because it
- * expired
- * @return {Number} The number of the media segment that contains
- * that time position.
- */
- var getMediaIndexForTime_ = function getMediaIndexForTime_(playlist, time, expired) {
- var i = undefined;
- var segment = undefined;
- var originalTime = time;
- var numSegments = playlist.segments.length;
- var lastSegment = numSegments - 1;
- var startIndex = undefined;
- var endIndex = undefined;
- var knownStart = undefined;
- var knownEnd = undefined;
- if (!playlist) {
- return 0;
- }
- // when the requested position is earlier than the current set of
- // segments, return the earliest segment index
- if (time < 0) {
- return 0;
- }
- expired = expired || 0;
- // find segments with known timing information that bound the
- // target time
- for (i = 0; i < numSegments; i++) {
- segment = playlist.segments[i];
- if (segment.end) {
- if (segment.end > time) {
- knownEnd = segment.end;
- endIndex = i;
- break;
- } else {
- knownStart = segment.end;
- startIndex = i + 1;
- }
- }
- }
- // time was equal to or past the end of the last segment in the playlist
- if (startIndex === numSegments) {
- return numSegments;
- }
- // use the bounds we just found and playlist information to
- // estimate the segment that contains the time we are looking for
- if (typeof startIndex !== 'undefined') {
- // We have a known-start point that is before our desired time so
- // walk from that point forwards
- time = time - knownStart;
- for (i = startIndex; i < (endIndex || numSegments); i++) {
- segment = playlist.segments[i];
- time -= segment.duration;
- if (time < 0) {
- return i;
- }
- }
- if (i >= endIndex) {
- // We haven't found a segment but we did hit a known end point
- // so fallback to interpolating between the segment index
- // based on the known span of the timeline we are dealing with
- // and the number of segments inside that span
- return startIndex + Math.floor((originalTime - knownStart) / (knownEnd - knownStart) * (endIndex - startIndex));
- }
- // We _still_ haven't found a segment so load the last one
- return lastSegment;
- } else if (typeof endIndex !== 'undefined') {
- // We _only_ have a known-end point that is after our desired time so
- // walk from that point backwards
- time = knownEnd - time;
- for (i = endIndex; i >= 0; i--) {
- segment = playlist.segments[i];
- time -= segment.duration;
- if (time < 0) {
- return i;
- }
- }
- // We haven't found a segment so load the first one if time is zero
- if (time === 0) {
- return 0;
- }
- return -1;
- }
- // We known nothing so walk from the front of the playlist,
- // subtracting durations until we find a segment that contains
- // time and return it
- time = time - expired;
- if (time < 0) {
- return -1;
- }
- for (i = 0; i < numSegments; i++) {
- segment = playlist.segments[i];
- time -= segment.duration;
- if (time < 0) {
- return i;
- }
- }
- // We are out of possible candidates so load the last one...
- // The last one is the least likely to overlap a buffer and therefore
- // the one most likely to tell us something about the timeline
- return lastSegment;
- };
- exports.getMediaIndexForTime_ = getMediaIndexForTime_;
- Playlist.duration = duration;
- Playlist.seekable = seekable;
- Playlist.getMediaIndexForTime_ = getMediaIndexForTime_;
- // exports
- exports['default'] = Playlist;
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{}],14:[function(require,module,exports){
- (function (global){
- /**
- * ranges
- *
- * Utilities for working with TimeRanges.
- *
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- // Fudge factor to account for TimeRanges rounding
- var TIME_FUDGE_FACTOR = 1 / 30;
- var filterRanges = function filterRanges(timeRanges, predicate) {
- var results = [];
- var i = undefined;
- if (timeRanges && timeRanges.length) {
- // Search for ranges that match the predicate
- for (i = 0; i < timeRanges.length; i++) {
- if (predicate(timeRanges.start(i), timeRanges.end(i))) {
- results.push([timeRanges.start(i), timeRanges.end(i)]);
- }
- }
- }
- return _videoJs2['default'].createTimeRanges(results);
- };
- /**
- * Attempts to find the buffered TimeRange that contains the specified
- * time.
- * @param {TimeRanges} buffered - the TimeRanges object to query
- * @param {number} time - the time to filter on.
- * @returns {TimeRanges} a new TimeRanges object
- */
- var findRange = function findRange(buffered, time) {
- return filterRanges(buffered, function (start, end) {
- return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
- });
- };
- /**
- * Returns the TimeRanges that begin at or later than the specified
- * time.
- * @param {TimeRanges} timeRanges - the TimeRanges object to query
- * @param {number} time - the time to filter on.
- * @returns {TimeRanges} a new TimeRanges object.
- */
- var findNextRange = function findNextRange(timeRanges, time) {
- return filterRanges(timeRanges, function (start) {
- return start - TIME_FUDGE_FACTOR >= time;
- });
- };
- /**
- * Search for a likely end time for the segment that was just appened
- * based on the state of the `buffered` property before and after the
- * append. If we fin only one such uncommon end-point return it.
- * @param {TimeRanges} original - the buffered time ranges before the update
- * @param {TimeRanges} update - the buffered time ranges after the update
- * @returns {Number|null} the end time added between `original` and `update`,
- * or null if one cannot be unambiguously determined.
- */
- var findSoleUncommonTimeRangesEnd = function findSoleUncommonTimeRangesEnd(original, update) {
- var i = undefined;
- var start = undefined;
- var end = undefined;
- var result = [];
- var edges = [];
- // In order to qualify as a possible candidate, the end point must:
- // 1) Not have already existed in the `original` ranges
- // 2) Not result from the shrinking of a range that already existed
- // in the `original` ranges
- // 3) Not be contained inside of a range that existed in `original`
- var overlapsCurrentEnd = function overlapsCurrentEnd(span) {
- return span[0] <= end && span[1] >= end;
- };
- if (original) {
- // Save all the edges in the `original` TimeRanges object
- for (i = 0; i < original.length; i++) {
- start = original.start(i);
- end = original.end(i);
- edges.push([start, end]);
- }
- }
- if (update) {
- // Save any end-points in `update` that are not in the `original`
- // TimeRanges object
- for (i = 0; i < update.length; i++) {
- start = update.start(i);
- end = update.end(i);
- if (edges.some(overlapsCurrentEnd)) {
- continue;
- }
- // at this point it must be a unique non-shrinking end edge
- result.push(end);
- }
- }
- // we err on the side of caution and return null if didn't find
- // exactly *one* differing end edge in the search above
- if (result.length !== 1) {
- return null;
- }
- return result[0];
- };
- /**
- * Calculate the intersection of two TimeRanges
- * @param {TimeRanges} bufferA
- * @param {TimeRanges} bufferB
- * @returns {TimeRanges} The interesection of `bufferA` with `bufferB`
- */
- var bufferIntersection = function bufferIntersection(bufferA, bufferB) {
- var start = null;
- var end = null;
- var arity = 0;
- var extents = [];
- var ranges = [];
- if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
- return _videoJs2['default'].createTimeRange();
- }
- // Handle the case where we have both buffers and create an
- // intersection of the two
- var count = bufferA.length;
- // A) Gather up all start and end times
- while (count--) {
- extents.push({ time: bufferA.start(count), type: 'start' });
- extents.push({ time: bufferA.end(count), type: 'end' });
- }
- count = bufferB.length;
- while (count--) {
- extents.push({ time: bufferB.start(count), type: 'start' });
- extents.push({ time: bufferB.end(count), type: 'end' });
- }
- // B) Sort them by time
- extents.sort(function (a, b) {
- return a.time - b.time;
- });
- // C) Go along one by one incrementing arity for start and decrementing
- // arity for ends
- for (count = 0; count < extents.length; count++) {
- if (extents[count].type === 'start') {
- arity++;
- // D) If arity is ever incremented to 2 we are entering an
- // overlapping range
- if (arity === 2) {
- start = extents[count].time;
- }
- } else if (extents[count].type === 'end') {
- arity--;
- // E) If arity is ever decremented to 1 we leaving an
- // overlapping range
- if (arity === 1) {
- end = extents[count].time;
- }
- }
- // F) Record overlapping ranges
- if (start !== null && end !== null) {
- ranges.push([start, end]);
- start = null;
- end = null;
- }
- }
- return _videoJs2['default'].createTimeRanges(ranges);
- };
- /**
- * Calculates the percentage of `segmentRange` that overlaps the
- * `buffered` time ranges.
- * @param {TimeRanges} segmentRange - the time range that the segment covers
- * @param {TimeRanges} buffered - the currently buffered time ranges
- * @returns {Number} percent of the segment currently buffered
- */
- var calculateBufferedPercent = function calculateBufferedPercent(segmentRange, buffered) {
- var segmentDuration = segmentRange.end(0) - segmentRange.start(0);
- var intersection = bufferIntersection(segmentRange, buffered);
- var overlapDuration = 0;
- var count = intersection.length;
- while (count--) {
- overlapDuration += intersection.end(count) - intersection.start(count);
- }
- return overlapDuration / segmentDuration * 100;
- };
- exports['default'] = {
- findRange: findRange,
- findNextRange: findNextRange,
- findSoleUncommonTimeRangesEnd: findSoleUncommonTimeRangesEnd,
- calculateBufferedPercent: calculateBufferedPercent,
- TIME_FUDGE_FACTOR: TIME_FUDGE_FACTOR
- };
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{}],15:[function(require,module,exports){
- /**
- * @file resolve-url.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _globalDocument = require('global/document');
- var _globalDocument2 = _interopRequireDefault(_globalDocument);
- /**
- * Constructs a new URI by interpreting a path relative to another
- * URI.
- *
- * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
- * @param {String} basePath a relative or absolute URI
- * @param {String} path a path part to combine with the base
- * @return {String} a URI that is equivalent to composing `base`
- * with `path`
- */
- var resolveUrl = function resolveUrl(basePath, path) {
- // use the base element to get the browser to handle URI resolution
- var oldBase = _globalDocument2['default'].querySelector('base');
- var docHead = _globalDocument2['default'].querySelector('head');
- var a = _globalDocument2['default'].createElement('a');
- var base = oldBase;
- var oldHref = undefined;
- var result = undefined;
- // prep the document
- if (oldBase) {
- oldHref = oldBase.href;
- } else {
- base = docHead.appendChild(_globalDocument2['default'].createElement('base'));
- }
- base.href = basePath;
- a.href = path;
- result = a.href;
- // clean up
- if (oldBase) {
- oldBase.href = oldHref;
- } else {
- docHead.removeChild(base);
- }
- return result;
- };
- exports['default'] = resolveUrl;
- module.exports = exports['default'];
- },{"global/document":21}],16:[function(require,module,exports){
- (function (global){
- /**
- * @file segment-loader.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _ranges = require('./ranges');
- var _ranges2 = _interopRequireDefault(_ranges);
- var _playlist = require('./playlist');
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- var _sourceUpdater = require('./source-updater');
- var _sourceUpdater2 = _interopRequireDefault(_sourceUpdater);
- var _decrypter = require('./decrypter');
- // in ms
- var CHECK_BUFFER_DELAY = 500;
- // the desired length of video to maintain in the buffer, in seconds
- var GOAL_BUFFER_LENGTH = 30;
- exports.GOAL_BUFFER_LENGTH = GOAL_BUFFER_LENGTH;
- /**
- * Updates segment with information about its end-point in time and, optionally,
- * the segment duration if we have enough information to determine a segment duration
- * accurately.
- *
- * @param {Object} playlist a media playlist object
- * @param {Number} segmentIndex the index of segment we last appended
- * @param {Number} segmentEnd the known of the segment referenced by segmentIndex
- */
- var updateSegmentMetadata = function updateSegmentMetadata(playlist, segmentIndex, segmentEnd) {
- if (!playlist) {
- return false;
- }
- var segment = playlist.segments[segmentIndex];
- var previousSegment = playlist.segments[segmentIndex - 1];
- if (segmentEnd && segment) {
- segment.end = segmentEnd;
- // fix up segment durations based on segment end data
- if (!previousSegment) {
- // first segment is always has a start time of 0 making its duration
- // equal to the segment end
- segment.duration = segment.end;
- } else if (previousSegment.end) {
- segment.duration = segment.end - previousSegment.end;
- }
- return true;
- }
- return false;
- };
- /**
- * Determines if we should call endOfStream on the media source based
- * on the state of the buffer or if appened segment was the final
- * segment in the playlist.
- *
- * @param {Object} playlist a media playlist object
- * @param {Object} mediaSource the MediaSource object
- * @param {Number} segmentIndex the index of segment we last appended
- * @param {Object} currentBuffered buffered region that currentTime resides in
- * @returns {Boolean} do we need to call endOfStream on the MediaSource
- */
- var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex, currentBuffered) {
- if (!playlist) {
- return false;
- }
- var segments = playlist.segments;
- // determine a few boolean values to help make the branch below easier
- // to read
- var appendedLastSegment = segmentIndex === segments.length - 1;
- var bufferedToEnd = currentBuffered.length && segments[segments.length - 1].end <= currentBuffered.end(0);
- // if we've buffered to the end of the video, we need to call endOfStream
- // so that MediaSources can trigger the `ended` event when it runs out of
- // buffered data instead of waiting for me
- return playlist.endList && mediaSource.readyState === 'open' && (appendedLastSegment || bufferedToEnd);
- };
- /* Turns segment byterange into a string suitable for use in
- * HTTP Range requests
- */
- var byterangeStr = function byterangeStr(byterange) {
- var byterangeStart = undefined;
- var byterangeEnd = undefined;
- // `byterangeEnd` is one less than `offset + length` because the HTTP range
- // header uses inclusive ranges
- byterangeEnd = byterange.offset + byterange.length - 1;
- byterangeStart = byterange.offset;
- return 'bytes=' + byterangeStart + '-' + byterangeEnd;
- };
- /* Defines headers for use in the xhr request for a particular segment.
- */
- var segmentXhrHeaders = function segmentXhrHeaders(segment) {
- var headers = {};
- if ('byterange' in segment) {
- headers.Range = byterangeStr(segment.byterange);
- }
- return headers;
- };
- /**
- * An object that manages segment loading and appending.
- *
- * @class SegmentLoader
- * @param {Object} options required and optional options
- * @extends videojs.EventTarget
- */
- var SegmentLoader = (function (_videojs$EventTarget) {
- _inherits(SegmentLoader, _videojs$EventTarget);
- function SegmentLoader(options) {
- _classCallCheck(this, SegmentLoader);
- _get(Object.getPrototypeOf(SegmentLoader.prototype), 'constructor', this).call(this);
- var settings = undefined;
- // check pre-conditions
- if (!options) {
- throw new TypeError('Initialization options are required');
- }
- if (typeof options.currentTime !== 'function') {
- throw new TypeError('No currentTime getter specified');
- }
- if (!options.mediaSource) {
- throw new TypeError('No MediaSource specified');
- }
- settings = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls, options);
- // public properties
- this.state = 'INIT';
- this.bandwidth = settings.bandwidth;
- this.roundTrip = NaN;
- this.bytesReceived = 0;
- // private properties
- this.hasPlayed_ = settings.hasPlayed;
- this.currentTime_ = settings.currentTime;
- this.seekable_ = settings.seekable;
- this.seeking_ = settings.seeking;
- this.setCurrentTime_ = settings.setCurrentTime;
- this.mediaSource_ = settings.mediaSource;
- this.withCredentials_ = settings.withCredentials;
- this.checkBufferTimeout_ = null;
- this.error_ = void 0;
- this.expired_ = 0;
- this.timeCorrection_ = 0;
- this.currentTimeline_ = -1;
- this.xhr_ = null;
- this.pendingSegment_ = null;
- this.sourceUpdater_ = null;
- this.hls_ = settings.hls;
- }
- /**
- * dispose of the SegmentLoader and reset to the default state
- */
- _createClass(SegmentLoader, [{
- key: 'dispose',
- value: function dispose() {
- this.state = 'DISPOSED';
- this.abort_();
- if (this.sourceUpdater_) {
- this.sourceUpdater_.dispose();
- }
- }
- /**
- * abort anything that is currently doing on with the SegmentLoader
- * and reset to a default state
- */
- }, {
- key: 'abort',
- value: function abort() {
- if (this.state !== 'WAITING') {
- return;
- }
- this.abort_();
- // don't wait for buffer check timeouts to begin fetching the
- // next segment
- if (!this.paused()) {
- this.state = 'READY';
- this.fillBuffer_();
- }
- }
- /**
- * set an error on the segment loader and null out any pending segements
- *
- * @param {Error} error the error to set on the SegmentLoader
- * @return {Error} the error that was set or that is currently set
- */
- }, {
- key: 'error',
- value: function error(_error) {
- if (typeof _error !== 'undefined') {
- this.error_ = _error;
- }
- this.pendingSegment_ = null;
- return this.error_;
- }
- /**
- * load a playlist and start to fill the buffer
- */
- }, {
- key: 'load',
- value: function load() {
- this.monitorBuffer_();
- // if we don't have a playlist yet, keep waiting for one to be
- // specified
- if (!this.playlist_) {
- return;
- }
- // if we're in the middle of processing a segment already, don't
- // kick off an additional segment request
- if (!this.sourceUpdater_ || this.state !== 'READY' && this.state !== 'INIT') {
- return;
- }
- this.state = 'READY';
- this.fillBuffer_();
- }
- /**
- * set a playlist on the segment loader
- *
- * @param {PlaylistLoader} media the playlist to set on the segment loader
- */
- }, {
- key: 'playlist',
- value: function playlist(media) {
- this.playlist_ = media;
- // if we were unpaused but waiting for a playlist, start
- // buffering now
- if (this.sourceUpdater_ && media && this.state === 'INIT' && !this.paused()) {
- this.state = 'READY';
- return this.fillBuffer_();
- }
- }
- /**
- * Prevent the loader from fetching additional segments. If there
- * is a segment request outstanding, it will finish processing
- * before the loader halts. A segment loader can be unpaused by
- * calling load().
- */
- }, {
- key: 'pause',
- value: function pause() {
- if (this.checkBufferTimeout_) {
- window.clearTimeout(this.checkBufferTimeout_);
- this.checkBufferTimeout_ = null;
- }
- }
- /**
- * Returns whether the segment loader is fetching additional
- * segments when given the opportunity. This property can be
- * modified through calls to pause() and load().
- */
- }, {
- key: 'paused',
- value: function paused() {
- return this.checkBufferTimeout_ === null;
- }
- /**
- * setter for expired time on the SegmentLoader
- *
- * @param {Number} expired the exired time to set
- */
- }, {
- key: 'expired',
- value: function expired(_expired) {
- this.expired_ = _expired;
- }
- /**
- * create/set the following mimetype on the SourceBuffer through a
- * SourceUpdater
- *
- * @param {String} mimeType the mime type string to use
- */
- }, {
- key: 'mimeType',
- value: function mimeType(_mimeType) {
- // TODO Allow source buffers to be re-created with different mime-types
- if (!this.sourceUpdater_) {
- this.sourceUpdater_ = new _sourceUpdater2['default'](this.mediaSource_, _mimeType);
- this.clearBuffer();
- // if we were unpaused but waiting for a sourceUpdater, start
- // buffering now
- if (this.playlist_ && this.state === 'INIT' && !this.paused()) {
- this.state = 'READY';
- return this.fillBuffer_();
- }
- }
- }
- /**
- * asynchronously/recursively monitor the buffer
- *
- * @private
- */
- }, {
- key: 'monitorBuffer_',
- value: function monitorBuffer_() {
- if (this.state === 'READY') {
- this.fillBuffer_();
- }
- if (this.checkBufferTimeout_) {
- window.clearTimeout(this.checkBufferTimeout_);
- }
- this.checkBufferTimeout_ = window.setTimeout(this.monitorBuffer_.bind(this), CHECK_BUFFER_DELAY);
- }
- /**
- * Return the amount of a segment specified by the mediaIndex overlaps
- * the current buffered content.
- *
- * @param {Object} playlist the playlist object to fetch segments from
- * @param {Number} mediaIndex the index of the segment in the playlist
- * @param {TimeRanges} buffered the state of the buffer
- * @returns {Number} percentage of the segment's time range that is
- * already in `buffered`
- */
- }, {
- key: 'getSegmentBufferedPercent_',
- value: function getSegmentBufferedPercent_(playlist, mediaIndex, currentTime, buffered) {
- var segment = playlist.segments[mediaIndex];
- var startOfSegment = (0, _playlist.duration)(playlist, playlist.mediaSequence + mediaIndex, this.expired_);
- var segmentRange = _videoJs2['default'].createTimeRanges([[Math.max(currentTime, startOfSegment), startOfSegment + segment.duration]]);
- return _ranges2['default'].calculateBufferedPercent(segmentRange, buffered);
- }
- /**
- * Determines what segment request should be made, given current
- * playback state.
- *
- * @param {TimeRanges} buffered - the state of the buffer
- * @param {Object} playlist - the playlist object to fetch segments from
- * @param {Number} currentTime - the playback position in seconds
- * @returns {Object} a segment info object that describes the
- * request that should be made or null if no request is necessary
- */
- }, {
- key: 'checkBuffer_',
- value: function checkBuffer_(buffered, playlist, currentTime) {
- var currentBuffered = _ranges2['default'].findRange(buffered, currentTime);
- // There are times when MSE reports the first segment as starting a
- // little after 0-time so add a fudge factor to try and fix those cases
- // or we end up fetching the same first segment over and over
- if (currentBuffered.length === 0 && currentTime === 0) {
- currentBuffered = _ranges2['default'].findRange(buffered, currentTime + _ranges2['default'].TIME_FUDGE_FACTOR);
- }
- var bufferedTime = undefined;
- var currentBufferedEnd = undefined;
- var timestampOffset = this.sourceUpdater_.timestampOffset();
- var segment = undefined;
- var mediaIndex = undefined;
- if (!playlist.segments.length) {
- return;
- }
- if (currentBuffered.length === 0) {
- // find the segment containing currentTime
- mediaIndex = (0, _playlist.getMediaIndexForTime_)(playlist, currentTime + this.timeCorrection_, this.expired_);
- } else {
- // find the segment adjacent to the end of the current
- // buffered region
- currentBufferedEnd = currentBuffered.end(0);
- bufferedTime = Math.max(0, currentBufferedEnd - currentTime);
- // if the video has not yet played only, and we already have
- // one segment downloaded do nothing
- if (!this.hasPlayed_() && bufferedTime >= 1) {
- return null;
- }
- // if there is plenty of content buffered, and the video has
- // been played before relax for awhile
- if (this.hasPlayed_() && bufferedTime >= GOAL_BUFFER_LENGTH) {
- return null;
- }
- mediaIndex = (0, _playlist.getMediaIndexForTime_)(playlist, currentBufferedEnd + this.timeCorrection_, this.expired_);
- }
- if (mediaIndex < 0 || mediaIndex === playlist.segments.length) {
- return null;
- }
- segment = playlist.segments[mediaIndex];
- var startOfSegment = (0, _playlist.duration)(playlist, playlist.mediaSequence + mediaIndex, this.expired_);
- // We will need to change timestampOffset of the sourceBuffer if either of
- // the following conditions are true:
- // - The segment.timeline !== this.currentTimeline
- // (we are crossing a discontinuity somehow)
- // - The "timestampOffset" for the start of this segment is less than
- // the currently set timestampOffset
- if (segment.timeline !== this.currentTimeline_ || startOfSegment < this.sourceUpdater_.timestampOffset()) {
- timestampOffset = startOfSegment;
- }
- return {
- // resolve the segment URL relative to the playlist
- uri: segment.resolvedUri,
- // the segment's mediaIndex at the time it was requested
- mediaIndex: mediaIndex,
- // the segment's playlist
- playlist: playlist,
- // unencrypted bytes of the segment
- bytes: null,
- // when a key is defined for this segment, the encrypted bytes
- encryptedBytes: null,
- // the state of the buffer before a segment is appended will be
- // stored here so that the actual segment duration can be
- // determined after it has been appended
- buffered: null,
- // The target timestampOffset for this segment when we append it
- // to the source buffer
- timestampOffset: timestampOffset,
- // The timeline that the segment is in
- timeline: segment.timeline
- };
- }
- /**
- * abort all pending xhr requests and null any pending segements
- *
- * @private
- */
- }, {
- key: 'abort_',
- value: function abort_() {
- if (this.xhr_) {
- this.xhr_.abort();
- }
- // clear out the segment being processed
- this.pendingSegment_ = null;
- }
- /**
- * fill the buffer with segements unless the
- * sourceBuffers are currently updating
- *
- * @private
- */
- }, {
- key: 'fillBuffer_',
- value: function fillBuffer_() {
- if (this.sourceUpdater_.updating()) {
- return;
- }
- // see if we need to begin loading immediately
- var request = this.checkBuffer_(this.sourceUpdater_.buffered(), this.playlist_, this.currentTime_(), this.timestampOffset_);
- if (!request) {
- return;
- }
- // Sanity check the segment-index determining logic by calcuating the
- // percentage of the chosen segment that is buffered. If more than 90%
- // of the segment is buffered then fetching it will likely not help in
- // any way
- var percentBuffered = this.getSegmentBufferedPercent_(this.playlist_, request.mediaIndex, this.currentTime_(), this.sourceUpdater_.buffered());
- if (percentBuffered >= 90) {
- // Increment the timeCorrection_ variable to push the fetcher forward
- // in time and hopefully skip any gaps or flaws in our understanding
- // of the media
- this.incrementTimeCorrection_(this.playlist_.targetDuration);
- if (!this.paused()) {
- this.fillBuffer_();
- }
- return;
- }
- this.loadSegment_(request);
- }
- /**
- * load a specific segment from a request into the buffer
- *
- * @private
- */
- }, {
- key: 'loadSegment_',
- value: function loadSegment_(segmentInfo) {
- var segment = undefined;
- var requestTimeout = undefined;
- var keyXhr = undefined;
- var segmentXhr = undefined;
- var seekable = this.seekable_();
- var currentTime = this.currentTime_();
- var removeToTime = 0;
- // Chrome has a hard limit of 150mb of
- // buffer and a very conservative "garbage collector"
- // We manually clear out the old buffer to ensure
- // we don't trigger the QuotaExceeded error
- // on the source buffer during subsequent appends
- // If we have a seekable range use that as the limit for what can be removed safely
- // otherwise remove anything older than 1 minute before the current play head
- if (seekable.length && seekable.start(0) > 0 && seekable.start(0) < currentTime) {
- removeToTime = seekable.start(0);
- } else {
- removeToTime = currentTime - 60;
- }
- if (removeToTime > 0) {
- this.sourceUpdater_.remove(0, removeToTime);
- }
- segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
- // Set xhr timeout to 150% of the segment duration to allow us
- // some time to switch renditions in the event of a catastrophic
- // decrease in network performance or a server issue.
- requestTimeout = segment.duration * 1.5 * 1000;
- if (segment.key) {
- keyXhr = this.hls_.xhr({
- uri: segment.key.resolvedUri,
- responseType: 'arraybuffer',
- withCredentials: this.withCredentials_,
- timeout: requestTimeout
- }, this.handleResponse_.bind(this));
- }
- this.pendingSegment_ = segmentInfo;
- segmentXhr = this.hls_.xhr({
- uri: segmentInfo.uri,
- responseType: 'arraybuffer',
- withCredentials: this.withCredentials_,
- timeout: requestTimeout,
- headers: segmentXhrHeaders(segment)
- }, this.handleResponse_.bind(this));
- this.xhr_ = {
- keyXhr: keyXhr,
- segmentXhr: segmentXhr,
- abort: function abort() {
- if (this.segmentXhr) {
- // Prevent error handler from running.
- this.segmentXhr.onreadystatechange = null;
- this.segmentXhr.abort();
- this.segmentXhr = null;
- }
- if (this.keyXhr) {
- // Prevent error handler from running.
- this.keyXhr.onreadystatechange = null;
- this.keyXhr.abort();
- this.keyXhr = null;
- }
- }
- };
- this.state = 'WAITING';
- }
- /**
- * triggered when a segment response is received
- *
- * @private
- */
- }, {
- key: 'handleResponse_',
- value: function handleResponse_(error, request) {
- var segmentInfo = undefined;
- var segment = undefined;
- var keyXhrRequest = undefined;
- var view = undefined;
- // timeout of previously aborted request
- if (!this.xhr_ || request !== this.xhr_.segmentXhr && request !== this.xhr_.keyXhr) {
- return;
- }
- segmentInfo = this.pendingSegment_;
- segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
- // if a request times out, reset bandwidth tracking
- if (request.timedout) {
- this.abort_();
- this.bandwidth = 1;
- this.roundTrip = NaN;
- this.state = 'READY';
- return this.trigger('progress');
- }
- // trigger an event for other errors
- if (!request.aborted && error) {
- // abort will clear xhr_
- keyXhrRequest = this.xhr_.keyXhr;
- this.abort_();
- this.error({
- status: request.status,
- message: request === keyXhrRequest ? 'HLS key request error at URL: ' + segment.key.uri : 'HLS segment request error at URL: ' + segmentInfo.uri,
- code: 2,
- xhr: request
- });
- this.state = 'READY';
- this.pause();
- return this.trigger('error');
- }
- // stop processing if the request was aborted
- if (!request.response) {
- this.abort_();
- return;
- }
- if (request === this.xhr_.segmentXhr) {
- // the segment request is no longer outstanding
- this.xhr_.segmentXhr = null;
- // calculate the download bandwidth based on segment request
- this.roundTrip = request.roundTripTime;
- this.bandwidth = request.bandwidth;
- this.bytesReceived += request.bytesReceived || 0;
- if (segment.key) {
- segmentInfo.encryptedBytes = new Uint8Array(request.response);
- } else {
- segmentInfo.bytes = new Uint8Array(request.response);
- }
- }
- if (request === this.xhr_.keyXhr) {
- keyXhrRequest = this.xhr_.segmentXhr;
- // the key request is no longer outstanding
- this.xhr_.keyXhr = null;
- if (request.response.byteLength !== 16) {
- this.abort_();
- this.error({
- status: request.status,
- message: 'Invalid HLS key at URL: ' + segment.key.uri,
- code: 2,
- xhr: request
- });
- this.state = 'READY';
- this.pause();
- return this.trigger('error');
- }
- view = new DataView(request.response);
- segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
- // if the media sequence is greater than 2^32, the IV will be incorrect
- // assuming 10s segments, that would be about 1300 years
- segment.key.iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
- }
- if (!this.xhr_.segmentXhr && !this.xhr_.keyXhr) {
- this.xhr_ = null;
- this.processResponse_();
- }
- }
- /**
- * clear anything that is currently in the buffer and throw it away
- */
- }, {
- key: 'clearBuffer',
- value: function clearBuffer() {
- if (this.sourceUpdater_ && this.sourceUpdater_.buffered().length) {
- this.sourceUpdater_.remove(0, Infinity);
- }
- }
- /**
- * Decrypt the segment that is being loaded if necessary
- *
- * @private
- */
- }, {
- key: 'processResponse_',
- value: function processResponse_() {
- var segmentInfo = undefined;
- var segment = undefined;
- this.state = 'DECRYPTING';
- segmentInfo = this.pendingSegment_;
- segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
- if (segment.key) {
- // this is an encrypted segment
- // incrementally decrypt the segment
- /* eslint-disable no-new, handle-callback-err */
- new _decrypter.Decrypter(segmentInfo.encryptedBytes, segment.key.bytes, segment.key.iv, (function (err, bytes) {
- // err always null
- segmentInfo.bytes = bytes;
- this.handleSegment_();
- }).bind(this));
- /* eslint-enable */
- } else {
- this.handleSegment_();
- }
- }
- /**
- * append a decrypted segement to the SourceBuffer through a SourceUpdater
- *
- * @private
- */
- }, {
- key: 'handleSegment_',
- value: function handleSegment_() {
- var segmentInfo = undefined;
- this.state = 'APPENDING';
- segmentInfo = this.pendingSegment_;
- segmentInfo.buffered = this.sourceUpdater_.buffered();
- this.currentTimeline_ = segmentInfo.timeline;
- if (segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
- this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset);
- }
- this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this));
- }
- /**
- * callback to run when appendBuffer is finished. detects if we are
- * in a good state to do things with the data we got, or if we need
- * to wait for more
- *
- * @private
- */
- }, {
- key: 'handleUpdateEnd_',
- value: function handleUpdateEnd_() {
- var segmentInfo = this.pendingSegment_;
- var currentTime = this.currentTime_();
- this.pendingSegment_ = null;
- // add segment metadata if it we have gained information during the
- // last append
- this.updateTimeline_(segmentInfo);
- this.trigger('progress');
- var currentMediaIndex = segmentInfo.mediaIndex;
- currentMediaIndex += segmentInfo.playlist.mediaSequence - this.playlist_.mediaSequence;
- var currentBuffered = _ranges2['default'].findRange(this.sourceUpdater_.buffered(), currentTime);
- // any time an update finishes and the last segment is in the
- // buffer, end the stream. this ensures the "ended" event will
- // fire if playback reaches that point.
- var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, currentMediaIndex, currentBuffered);
- if (isEndOfStream) {
- this.mediaSource_.endOfStream();
- }
- // when seeking to the beginning of the seekable range, it's
- // possible that imprecise timing information may cause the seek to
- // end up earlier than the start of the range
- // in that case, seek again
- var seekable = this.seekable_();
- var next = _ranges2['default'].findNextRange(this.sourceUpdater_.buffered(), currentTime);
- if (this.seeking_() && currentBuffered.length === 0) {
- if (seekable.length && currentTime < seekable.start(0)) {
- if (next.length) {
- _videoJs2['default'].log('tried seeking to', currentTime, 'but that was too early, retrying at', next.start(0));
- this.setCurrentTime_(next.start(0) + _ranges2['default'].TIME_FUDGE_FACTOR);
- }
- }
- }
- this.state = 'READY';
- if (!this.paused()) {
- this.fillBuffer_();
- }
- }
- /**
- * annotate the segment with any start and end time information
- * added by the media processing
- *
- * @private
- * @param {Object} segmentInfo annotate a segment with time info
- */
- }, {
- key: 'updateTimeline_',
- value: function updateTimeline_(segmentInfo) {
- var segment = undefined;
- var segmentEnd = undefined;
- var timelineUpdated = undefined;
- var segmentLength = this.playlist_.targetDuration;
- var playlist = segmentInfo.playlist;
- var currentMediaIndex = segmentInfo.mediaIndex;
- currentMediaIndex += playlist.mediaSequence - this.playlist_.mediaSequence;
- segment = playlist.segments[currentMediaIndex];
- // Update segment meta-data (duration and end-point) based on timeline
- if (segment && segmentInfo && segmentInfo.playlist.uri === this.playlist_.uri) {
- segmentEnd = _ranges2['default'].findSoleUncommonTimeRangesEnd(segmentInfo.buffered, this.sourceUpdater_.buffered());
- timelineUpdated = updateSegmentMetadata(playlist, currentMediaIndex, segmentEnd);
- segmentLength = segment.duration;
- }
- // the last segment append must have been entirely in the
- // already buffered time ranges. adjust the timeCorrection
- // offset to fetch forward until we find a segment that adds
- // to the buffered time ranges and improves subsequent media
- // index calculations.
- if (!timelineUpdated) {
- this.incrementTimeCorrection_(segmentLength);
- } else {
- this.timeCorrection_ = 0;
- }
- }
- /**
- * add a number of seconds to the currentTime when determining which
- * segment to fetch in order to force the fetcher to advance in cases
- * where it may get stuck on the same segment due to buffer gaps or
- * missing segment annotation after a rendition switch (especially
- * during a live stream)
- *
- * @private
- * @param {Number} secondsToIncrement number of seconds to add to the
- * timeCorrection_ variable
- */
- }, {
- key: 'incrementTimeCorrection_',
- value: function incrementTimeCorrection_(secondsToIncrement) {
- // If we have already incremented timeCorrection_ beyond the limit,
- // then stop trying to find a segment, pause fetching, and emit an
- // error event
- if (this.timeCorrection_ >= this.playlist_.targetDuration * 5) {
- this.timeCorrection_ = 0;
- this.pause();
- return this.trigger('error');
- }
- this.timeCorrection_ += secondsToIncrement;
- }
- }]);
- return SegmentLoader;
- })(_videoJs2['default'].EventTarget);
- exports['default'] = SegmentLoader;
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./decrypter":5,"./playlist":13,"./ranges":14,"./source-updater":17}],17:[function(require,module,exports){
- (function (global){
- /**
- * @file source-updater.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- /**
- * A queue of callbacks to be serialized and applied when a
- * MediaSource and its associated SourceBuffers are not in the
- * updating state. It is used by the segment loader to update the
- * underlying SourceBuffers when new data is loaded, for instance.
- *
- * @class SourceUpdater
- * @param {MediaSource} mediaSource the MediaSource to create the
- * SourceBuffer from
- * @param {String} mimeType the desired MIME type of the underlying
- * SourceBuffer
- */
- var SourceUpdater = (function () {
- function SourceUpdater(mediaSource, mimeType) {
- var _this = this;
- _classCallCheck(this, SourceUpdater);
- var createSourceBuffer = function createSourceBuffer() {
- _this.sourceBuffer_ = mediaSource.addSourceBuffer(mimeType);
- // run completion handlers and process callbacks as updateend
- // events fire
- _this.sourceBuffer_.addEventListener('updateend', function () {
- var pendingCallback = _this.pendingCallback_;
- _this.pendingCallback_ = null;
- if (pendingCallback) {
- pendingCallback();
- }
- });
- _this.sourceBuffer_.addEventListener('updateend', _this.runCallback_.bind(_this));
- _this.runCallback_();
- };
- this.callbacks_ = [];
- this.pendingCallback_ = null;
- this.timestampOffset_ = 0;
- this.mediaSource = mediaSource;
- if (mediaSource.readyState === 'closed') {
- mediaSource.addEventListener('sourceopen', createSourceBuffer);
- } else {
- createSourceBuffer();
- }
- }
- /**
- * Aborts the current segment and resets the segment parser.
- *
- * @param {Function} done function to call when done
- * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
- */
- _createClass(SourceUpdater, [{
- key: 'abort',
- value: function abort(done) {
- var _this2 = this;
- this.queueCallback_(function () {
- _this2.sourceBuffer_.abort();
- }, done);
- }
- /**
- * Queue an update to append an ArrayBuffer.
- *
- * @param {ArrayBuffer} bytes
- * @param {Function} done the function to call when done
- * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
- */
- }, {
- key: 'appendBuffer',
- value: function appendBuffer(bytes, done) {
- var _this3 = this;
- this.queueCallback_(function () {
- _this3.sourceBuffer_.appendBuffer(bytes);
- }, done);
- }
- /**
- * Indicates what TimeRanges are buffered in the managed SourceBuffer.
- *
- * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
- */
- }, {
- key: 'buffered',
- value: function buffered() {
- if (!this.sourceBuffer_) {
- return _videoJs2['default'].createTimeRanges();
- }
- return this.sourceBuffer_.buffered;
- }
- /**
- * Queue an update to set the duration.
- *
- * @param {Double} duration what to set the duration to
- * @see http://www.w3.org/TR/media-source/#widl-MediaSource-duration
- */
- }, {
- key: 'duration',
- value: function duration(_duration) {
- var _this4 = this;
- this.queueCallback_(function () {
- _this4.sourceBuffer_.duration = _duration;
- });
- }
- /**
- * Queue an update to remove a time range from the buffer.
- *
- * @param {Number} start where to start the removal
- * @param {Number} end where to end the removal
- * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
- */
- }, {
- key: 'remove',
- value: function remove(start, end) {
- var _this5 = this;
- this.queueCallback_(function () {
- _this5.sourceBuffer_.remove(start, end);
- });
- }
- /**
- * wether the underlying sourceBuffer is updating or not
- *
- * @return {Boolean} the updating status of the SourceBuffer
- */
- }, {
- key: 'updating',
- value: function updating() {
- return !this.sourceBuffer_ || this.sourceBuffer_.updating;
- }
- /**
- * Set/get the timestampoffset on the SourceBuffer
- *
- * @return {Number} the timestamp offset
- */
- }, {
- key: 'timestampOffset',
- value: function timestampOffset(offset) {
- var _this6 = this;
- if (typeof offset !== 'undefined') {
- this.queueCallback_(function () {
- _this6.sourceBuffer_.timestampOffset = offset;
- });
- this.timestampOffset_ = offset;
- }
- return this.timestampOffset_;
- }
- /**
- * que a callback to run
- */
- }, {
- key: 'queueCallback_',
- value: function queueCallback_(callback, done) {
- this.callbacks_.push([callback.bind(this), done]);
- this.runCallback_();
- }
- /**
- * run a queued callback
- */
- }, {
- key: 'runCallback_',
- value: function runCallback_() {
- var callbacks = undefined;
- if (this.sourceBuffer_ && !this.sourceBuffer_.updating && this.callbacks_.length) {
- callbacks = this.callbacks_.shift();
- this.pendingCallback_ = callbacks[1];
- callbacks[0]();
- }
- }
- /**
- * dispose of the source updater and the underlying sourceBuffer
- */
- }, {
- key: 'dispose',
- value: function dispose() {
- if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') {
- this.sourceBuffer_.abort();
- }
- }
- }]);
- return SourceUpdater;
- })();
- exports['default'] = SourceUpdater;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{}],18:[function(require,module,exports){
- /**
- * @file stream.js
- */
- /**
- * A lightweight readable stream implemention that handles event dispatching.
- *
- * @class Stream
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- var Stream = (function () {
- function Stream() {
- _classCallCheck(this, Stream);
- this.listeners = {};
- }
- /**
- * Add a listener for a specified event type.
- *
- * @param {String} type the event name
- * @param {Function} listener the callback to be invoked when an event of
- * the specified type occurs
- */
- _createClass(Stream, [{
- key: 'on',
- value: function on(type, listener) {
- if (!this.listeners[type]) {
- this.listeners[type] = [];
- }
- this.listeners[type].push(listener);
- }
- /**
- * Remove a listener for a specified event type.
- *
- * @param {String} type the event name
- * @param {Function} listener a function previously registered for this
- * type of event through `on`
- * @return {Boolean} if we could turn it off or not
- */
- }, {
- key: 'off',
- value: function off(type, listener) {
- var index = undefined;
- if (!this.listeners[type]) {
- return false;
- }
- index = this.listeners[type].indexOf(listener);
- this.listeners[type].splice(index, 1);
- return index > -1;
- }
- /**
- * Trigger an event of the specified type on this stream. Any additional
- * arguments to this function are passed as parameters to event listeners.
- *
- * @param {String} type the event name
- */
- }, {
- key: 'trigger',
- value: function trigger(type) {
- var callbacks = undefined;
- var i = undefined;
- var length = undefined;
- var args = undefined;
- callbacks = this.listeners[type];
- if (!callbacks) {
- return;
- }
- // Slicing the arguments on every invocation of this method
- // can add a significant amount of overhead. Avoid the
- // intermediate object creation for the common case of a
- // single callback argument
- if (arguments.length === 2) {
- length = callbacks.length;
- for (i = 0; i < length; ++i) {
- callbacks[i].call(this, arguments[1]);
- }
- } else {
- args = Array.prototype.slice.call(arguments, 1);
- length = callbacks.length;
- for (i = 0; i < length; ++i) {
- callbacks[i].apply(this, args);
- }
- }
- }
- /**
- * Destroys the stream and cleans up.
- */
- }, {
- key: 'dispose',
- value: function dispose() {
- this.listeners = {};
- }
- /**
- * Forwards all `data` events on this stream to the destination stream. The
- * destination stream should provide a method `push` to receive the data
- * events as they arrive.
- *
- * @param {Stream} destination the stream that will receive all `data` events
- * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
- */
- }, {
- key: 'pipe',
- value: function pipe(destination) {
- this.on('data', function (data) {
- destination.push(data);
- });
- }
- }]);
- return Stream;
- })();
- exports['default'] = Stream;
- module.exports = exports['default'];
- },{}],19:[function(require,module,exports){
- (function (global){
- /**
- * @file xhr.js
- */
- /**
- * A wrapper for videojs.xhr that tracks bandwidth.
- *
- * @param {Object} options options for the XHR
- * @param {Function} callback the callback to call when done
- * @return {Request} the xhr request that is going to be made
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var xhrFactory = function xhrFactory() {
- var xhr = function XhrFunction(options, callback) {
- // Add a default timeout for all hls requests
- options = (0, _videoJs.mergeOptions)({
- timeout: 45e3
- }, options);
- // Allow an optional user-specified function to modify the option
- // object before we construct the xhr request
- if (XhrFunction.beforeRequest && typeof XhrFunction.beforeRequest === 'function') {
- var newOptions = XhrFunction.beforeRequest(options);
- if (newOptions) {
- options = newOptions;
- }
- }
- var request = (0, _videoJs.xhr)(options, function (error, response) {
- if (!error && request.response) {
- request.responseTime = new Date().getTime();
- request.roundTripTime = request.responseTime - request.requestTime;
- request.bytesReceived = request.response.byteLength || request.response.length;
- if (!request.bandwidth) {
- request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
- }
- }
- // videojs.xhr now uses a specific code
- // on the error object to signal that a request has
- // timed out errors of setting a boolean on the request object
- if (error || request.timedout) {
- request.timedout = request.timedout || error.code === 'ETIMEDOUT';
- } else {
- request.timedout = false;
- }
- // videojs.xhr no longer considers status codes outside of 200 and 0
- // (for file uris) to be errors, but the old XHR did, so emulate that
- // behavior. Status 206 may be used in response to byterange requests.
- if (!error && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
- error = new Error('XHR Failed with a response of: ' + (request && (request.response || request.responseText)));
- }
- callback(error, request);
- });
- request.requestTime = new Date().getTime();
- return request;
- };
- return xhr;
- };
- exports['default'] = xhrFactory;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{}],20:[function(require,module,exports){
- },{}],21:[function(require,module,exports){
- (function (global){
- var topLevel = typeof global !== 'undefined' ? global :
- typeof window !== 'undefined' ? window : {}
- var minDoc = require('min-document');
- if (typeof document !== 'undefined') {
- module.exports = document;
- } else {
- var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
- if (!doccy) {
- doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
- }
- module.exports = doccy;
- }
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"min-document":20}],22:[function(require,module,exports){
- /*
- * pkcs7.pad
- * https://github.com/brightcove/pkcs7
- *
- * Copyright (c) 2014 Brightcove
- * Licensed under the apache2 license.
- */
- 'use strict';
- var PADDING;
- /**
- * Returns a new Uint8Array that is padded with PKCS#7 padding.
- * @param plaintext {Uint8Array} the input bytes before encryption
- * @return {Uint8Array} the padded bytes
- * @see http://tools.ietf.org/html/rfc5652
- */
- module.exports = function pad(plaintext) {
- var padding = PADDING[(plaintext.byteLength % 16) || 0],
- result = new Uint8Array(plaintext.byteLength + padding.length);
- result.set(plaintext);
- result.set(padding, plaintext.byteLength);
- return result;
- };
- // pre-define the padding values
- PADDING = [
- [16, 16, 16, 16,
- 16, 16, 16, 16,
- 16, 16, 16, 16,
- 16, 16, 16, 16],
- [15, 15, 15, 15,
- 15, 15, 15, 15,
- 15, 15, 15, 15,
- 15, 15, 15],
- [14, 14, 14, 14,
- 14, 14, 14, 14,
- 14, 14, 14, 14,
- 14, 14],
- [13, 13, 13, 13,
- 13, 13, 13, 13,
- 13, 13, 13, 13,
- 13],
- [12, 12, 12, 12,
- 12, 12, 12, 12,
- 12, 12, 12, 12],
- [11, 11, 11, 11,
- 11, 11, 11, 11,
- 11, 11, 11],
- [10, 10, 10, 10,
- 10, 10, 10, 10,
- 10, 10],
- [9, 9, 9, 9,
- 9, 9, 9, 9,
- 9],
- [8, 8, 8, 8,
- 8, 8, 8, 8],
- [7, 7, 7, 7,
- 7, 7, 7],
- [6, 6, 6, 6,
- 6, 6],
- [5, 5, 5, 5,
- 5],
- [4, 4, 4, 4],
- [3, 3, 3],
- [2, 2],
- [1]
- ];
- },{}],23:[function(require,module,exports){
- /*
- * pkcs7
- * https://github.com/brightcove/pkcs7
- *
- * Copyright (c) 2014 Brightcove
- * Licensed under the apache2 license.
- */
- 'use strict';
- exports.pad = require('./pad.js');
- exports.unpad = require('./unpad.js');
- },{"./pad.js":22,"./unpad.js":24}],24:[function(require,module,exports){
- /*
- * pkcs7.unpad
- * https://github.com/brightcove/pkcs7
- *
- * Copyright (c) 2014 Brightcove
- * Licensed under the apache2 license.
- */
- 'use strict';
- /**
- * Returns the subarray of a Uint8Array without PKCS#7 padding.
- * @param padded {Uint8Array} unencrypted bytes that have been padded
- * @return {Uint8Array} the unpadded bytes
- * @see http://tools.ietf.org/html/rfc5652
- */
- module.exports = function unpad(padded) {
- return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
- };
- },{}],25:[function(require,module,exports){
- (function (global){
- /**
- * @file add-text-track-data.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- /**
- * Define properties on a cue for backwards compatability,
- * but warn the user that the way that they are using it
- * is depricated and will be removed at a later date.
- *
- * @param {Cue} cue the cue to add the properties on
- * @private
- */
- var deprecateOldCue = function deprecateOldCue(cue) {
- Object.defineProperties(cue.frame, {
- id: {
- get: function get() {
- _videoJs2['default'].log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
- return cue.value.key;
- }
- },
- value: {
- get: function get() {
- _videoJs2['default'].log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
- return cue.value.data;
- }
- },
- privateData: {
- get: function get() {
- _videoJs2['default'].log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
- return cue.value.data;
- }
- }
- });
- };
- /**
- * Add text track data to a source handler given the captions and
- * metadata from the buffer.
- *
- * @param {Object} sourceHandler the flash or virtual source buffer
- * @param {Array} captionArray an array of caption data
- * @param {Array} cue an array of meta data
- * @private
- */
- var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
- var Cue = window.WebKitDataCue || window.VTTCue;
- if (captionArray) {
- captionArray.forEach(function (caption) {
- this.inbandTextTrack_.addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
- }, sourceHandler);
- }
- if (metadataArray) {
- metadataArray.forEach(function (metadata) {
- var time = metadata.cueTime + this.timestampOffset;
- metadata.frames.forEach(function (frame) {
- var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
- cue.frame = frame;
- cue.value = frame;
- deprecateOldCue(cue);
- this.metadataTrack_.addCue(cue);
- }, this);
- }, sourceHandler);
- }
- };
- exports['default'] = addTextTrackData;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{}],26:[function(require,module,exports){
- /**
- * @file codec-utils.js
- */
- /**
- * Check if a codec string refers to an audio codec.
- *
- * @param {String} codec codec string to check
- * @return {Boolean} if this is an audio codec
- * @private
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var isAudioCodec = function isAudioCodec(codec) {
- return (/mp4a\.\d+.\d+/i.test(codec)
- );
- };
- /**
- * Check if a codec string refers to a video codec.
- *
- * @param {String} codec codec string to check
- * @return {Boolean} if this is a video codec
- * @private
- */
- var isVideoCodec = function isVideoCodec(codec) {
- return (/avc1\.[\da-f]+/i.test(codec)
- );
- };
- /**
- * Parse a content type header into a type and parameters
- * object
- *
- * @param {String} type the content type header
- * @return {Object} the parsed content-type
- * @private
- */
- var parseContentType = function parseContentType(type) {
- var object = { type: '', parameters: {} };
- var parameters = type.trim().split(';');
- // first parameter should always be content-type
- object.type = parameters.shift().trim();
- parameters.forEach(function (parameter) {
- var pair = parameter.trim().split('=');
- if (pair.length > 1) {
- var _name = pair[0].replace(/"/g, '').trim();
- var value = pair[1].replace(/"/g, '').trim();
- object.parameters[_name] = value;
- }
- });
- return object;
- };
- exports['default'] = {
- isAudioCodec: isAudioCodec,
- parseContentType: parseContentType,
- isVideoCodec: isVideoCodec
- };
- module.exports = exports['default'];
- },{}],27:[function(require,module,exports){
- /**
- * @file create-text-tracks-if-necessary.js
- */
- /**
- * Create text tracks on video.js if they exist on a segment.
- *
- * @param {Object} sourceBuffer the VSB or FSB
- * @param {Object} mediaSource the HTML or Flash media source
- * @param {Object} segment the segment that may contain the text track
- * @private
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
- // create an in-band caption track if one is present in the segment
- if (segment.captions && segment.captions.length && !sourceBuffer.inbandTextTrack_) {
- sourceBuffer.inbandTextTrack_ = mediaSource.player_.addTextTrack('captions', 'cc1');
- }
- if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
- sourceBuffer.metadataTrack_ = mediaSource.player_.addTextTrack('metadata', 'Timed Metadata');
- sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
- }
- };
- exports['default'] = createTextTracksIfNecessary;
- module.exports = exports['default'];
- },{}],28:[function(require,module,exports){
- /**
- * @file flash-constants.js
- */
- /**
- * The maximum size in bytes for append operations to the video.js
- * SWF. Calling through to Flash blocks and can be expensive so
- * tuning this parameter may improve playback on slower
- * systems. There are two factors to consider:
- * - Each interaction with the SWF must be quick or you risk dropping
- * video frames. To maintain 60fps for the rest of the page, each append
- * must not take longer than 16ms. Given the likelihood that the page
- * will be executing more javascript than just playback, you probably
- * want to aim for less than 8ms. We aim for just 4ms.
- * - Bigger appends significantly increase throughput. The total number of
- * bytes over time delivered to the SWF must exceed the video bitrate or
- * playback will stall.
- *
- * We adaptively tune the size of appends to give the best throughput
- * possible given the performance of the system. To do that we try to append
- * as much as possible in TIME_PER_TICK and while tuning the size of appends
- * dynamically so that we only append about 4-times in that 4ms span.
- *
- * The reason we try to keep the number of appends around four is due to
- * externalities such as Flash load and garbage collection that are highly
- * variable and having 4 iterations allows us to exit the loop early if
- * an iteration takes longer than expected.
- *
- * @private
- */
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- var flashConstants = {
- TIME_BETWEEN_TICKS: Math.floor(1000 / 480),
- TIME_PER_TICK: Math.floor(1000 / 240),
- // 1kb
- BYTES_PER_CHUNK: 1 * 1024,
- MIN_CHUNK: 1024,
- MAX_CHUNK: 1024 * 1024
- };
- exports["default"] = flashConstants;
- module.exports = exports["default"];
- },{}],29:[function(require,module,exports){
- (function (global){
- /**
- * @file flash-media-source.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- var _flashSourceBuffer = require('./flash-source-buffer');
- var _flashSourceBuffer2 = _interopRequireDefault(_flashSourceBuffer);
- var _flashConstants = require('./flash-constants');
- var _flashConstants2 = _interopRequireDefault(_flashConstants);
- var _codecUtils = require('./codec-utils');
- /**
- * A flash implmentation of HTML MediaSources and a polyfill
- * for browsers that don't support native or HTML MediaSources..
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
- * @class FlashMediaSource
- * @extends videojs.EventTarget
- */
- var FlashMediaSource = (function (_videojs$EventTarget) {
- _inherits(FlashMediaSource, _videojs$EventTarget);
- function FlashMediaSource() {
- var _this = this;
- _classCallCheck(this, FlashMediaSource);
- _get(Object.getPrototypeOf(FlashMediaSource.prototype), 'constructor', this).call(this);
- this.sourceBuffers = [];
- this.readyState = 'closed';
- this.on(['sourceopen', 'webkitsourceopen'], function (event) {
- // find the swf where we will push media data
- _this.swfObj = document.getElementById(event.swfId);
- _this.player_ = (0, _videoJs2['default'])(_this.swfObj.parentNode);
- _this.tech_ = _this.swfObj.tech;
- _this.readyState = 'open';
- _this.tech_.on('seeking', function () {
- var i = _this.sourceBuffers.length;
- while (i--) {
- _this.sourceBuffers[i].abort();
- }
- });
- // trigger load events
- if (_this.swfObj) {
- _this.swfObj.vjs_load();
- }
- });
- }
- /**
- * Set or return the presentation duration.
- *
- * @param {Double} value the duration of the media in seconds
- * @param {Double} the current presentation duration
- * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration
- */
- /**
- * We have this function so that the html and flash interfaces
- * are the same.
- *
- * @private
- */
- _createClass(FlashMediaSource, [{
- key: 'addSeekableRange_',
- value: function addSeekableRange_() {}
- // intentional no-op
- /**
- * Create a new flash source buffer and add it to our flash media source.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
- * @param {String} type the content-type of the source
- * @return {Object} the flash source buffer
- */
- }, {
- key: 'addSourceBuffer',
- value: function addSourceBuffer(type) {
- var parsedType = (0, _codecUtils.parseContentType)(type);
- var sourceBuffer = undefined;
- // if this is an FLV type, we'll push data to flash
- if (parsedType.type === 'video/mp2t') {
- // Flash source buffers
- sourceBuffer = new _flashSourceBuffer2['default'](this);
- } else {
- throw new Error('NotSupportedError (Video.js)');
- }
- this.sourceBuffers.push(sourceBuffer);
- return sourceBuffer;
- }
- /**
- * Signals the end of the stream.
- *
- * @link https://w3c.github.io/media-source/#widl-MediaSource-endOfStream-void-EndOfStreamError-error
- * @param {String=} error Signals that a playback error
- * has occurred. If specified, it must be either "network" or
- * "decode".
- */
- }, {
- key: 'endOfStream',
- value: function endOfStream(error) {
- if (error === 'network') {
- // MEDIA_ERR_NETWORK
- this.tech_.error(2);
- } else if (error === 'decode') {
- // MEDIA_ERR_DECODE
- this.tech_.error(3);
- }
- if (this.readyState !== 'ended') {
- this.readyState = 'ended';
- this.swfObj.vjs_endOfStream();
- }
- }
- }]);
- return FlashMediaSource;
- })(_videoJs2['default'].EventTarget);
- exports['default'] = FlashMediaSource;
- try {
- Object.defineProperty(FlashMediaSource.prototype, 'duration', {
- /**
- * Return the presentation duration.
- *
- * @return {Double} the duration of the media in seconds
- * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration
- */
- get: function get() {
- if (!this.swfObj) {
- return NaN;
- }
- // get the current duration from the SWF
- return this.swfObj.vjs_getProperty('duration');
- },
- /**
- * Set the presentation duration.
- *
- * @param {Double} value the duration of the media in seconds
- * @return {Double} the duration of the media in seconds
- * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration
- */
- set: function set(value) {
- var i = undefined;
- var oldDuration = this.swfObj.vjs_getProperty('duration');
- this.swfObj.vjs_setProperty('duration', value);
- if (value < oldDuration) {
- // In MSE, this triggers the range removal algorithm which causes
- // an update to occur
- for (i = 0; i < this.sourceBuffers.length; i++) {
- this.sourceBuffers[i].remove(value, oldDuration);
- }
- }
- return value;
- }
- });
- } catch (e) {
- // IE8 throws if defineProperty is called on a non-DOM node. We
- // don't support IE8 but we shouldn't throw an error if loaded
- // there.
- FlashMediaSource.prototype.duration = NaN;
- }
- for (var property in _flashConstants2['default']) {
- FlashMediaSource[property] = _flashConstants2['default'][property];
- }
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./codec-utils":26,"./flash-constants":28,"./flash-source-buffer":30}],30:[function(require,module,exports){
- (function (global){
- /**
- * @file flash-source-buffer.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- var _muxJs = require('mux.js');
- var _muxJs2 = _interopRequireDefault(_muxJs);
- var _removeCuesFromTrack = require('./remove-cues-from-track');
- var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack);
- var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary');
- var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary);
- var _addTextTrackData = require('./add-text-track-data');
- var _addTextTrackData2 = _interopRequireDefault(_addTextTrackData);
- var _flashConstants = require('./flash-constants');
- var _flashConstants2 = _interopRequireDefault(_flashConstants);
- /**
- * A wrapper around the setTimeout function that uses
- * the flash constant time between ticks value.
- *
- * @param {Function} func the function callback to run
- * @private
- */
- var scheduleTick = function scheduleTick(func) {
- // Chrome doesn't invoke requestAnimationFrame callbacks
- // in background tabs, so use setTimeout.
- window.setTimeout(func, _flashConstants2['default'].TIME_BETWEEN_TICKS);
- };
- /**
- * Round a number to a specified number of places much like
- * toFixed but return a number instead of a string representation.
- *
- * @param {Number} num A number
- * @param {Number} places The number of decimal places which to
- * round
- * @private
- */
- var toDecimalPlaces = function toDecimalPlaces(num, places) {
- if (typeof places !== 'number' || places < 0) {
- places = 0;
- }
- var scale = Math.pow(10, places);
- return Math.round(num * scale) / scale;
- };
- /**
- * A SourceBuffer implementation for Flash rather than HTML.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
- * @param {Object} mediaSource the flash media source
- * @class FlashSourceBuffer
- * @extends videojs.EventTarget
- */
- var FlashSourceBuffer = (function (_videojs$EventTarget) {
- _inherits(FlashSourceBuffer, _videojs$EventTarget);
- function FlashSourceBuffer(mediaSource) {
- var _this = this;
- _classCallCheck(this, FlashSourceBuffer);
- _get(Object.getPrototypeOf(FlashSourceBuffer.prototype), 'constructor', this).call(this);
- var encodedHeader = undefined;
- // Start off using the globally defined value but refine
- // as we append data into flash
- this.chunkSize_ = _flashConstants2['default'].BYTES_PER_CHUNK;
- // byte arrays queued to be appended
- this.buffer_ = [];
- // the total number of queued bytes
- this.bufferSize_ = 0;
- // to be able to determine the correct position to seek to, we
- // need to retain information about the mapping between the
- // media timeline and PTS values
- this.basePtsOffset_ = NaN;
- this.mediaSource = mediaSource;
- // indicates whether the asynchronous continuation of an operation
- // is still being processed
- // see https://w3c.github.io/media-source/#widl-SourceBuffer-updating
- this.updating = false;
- this.timestampOffset_ = 0;
- // TS to FLV transmuxer
- this.segmentParser_ = new _muxJs2['default'].flv.Transmuxer();
- this.segmentParser_.on('data', this.receiveBuffer_.bind(this));
- encodedHeader = window.btoa(String.fromCharCode.apply(null, Array.prototype.slice.call(this.segmentParser_.getFlvHeader())));
- this.mediaSource.swfObj.vjs_appendBuffer(encodedHeader);
- Object.defineProperty(this, 'timestampOffset', {
- get: function get() {
- return this.timestampOffset_;
- },
- set: function set(val) {
- if (typeof val === 'number' && val >= 0) {
- this.timestampOffset_ = val;
- this.segmentParser_ = new _muxJs2['default'].flv.Transmuxer();
- this.segmentParser_.on('data', this.receiveBuffer_.bind(this));
- // We have to tell flash to expect a discontinuity
- this.mediaSource.swfObj.vjs_discontinuity();
- // the media <-> PTS mapping must be re-established after
- // the discontinuity
- this.basePtsOffset_ = NaN;
- }
- }
- });
- Object.defineProperty(this, 'buffered', {
- get: function get() {
- if (!this.mediaSource || !this.mediaSource.swfObj || !('vjs_getProperty' in this.mediaSource.swfObj)) {
- return _videoJs2['default'].createTimeRange();
- }
- var buffered = this.mediaSource.swfObj.vjs_getProperty('buffered');
- if (buffered && buffered.length) {
- buffered[0][0] = toDecimalPlaces(buffered[0][0], 3);
- buffered[0][1] = toDecimalPlaces(buffered[0][1], 3);
- }
- return _videoJs2['default'].createTimeRanges(buffered);
- }
- });
- // On a seek we remove all text track data since flash has no concept
- // of a buffered-range and everything else is reset on seek
- this.mediaSource.player_.on('seeked', function () {
- (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.metadataTrack_);
- (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.inbandTextTrack_);
- });
- }
- /**
- * Append bytes to the sourcebuffers buffer, in this case we
- * have to append it to swf object.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
- * @param {Array} bytes
- */
- _createClass(FlashSourceBuffer, [{
- key: 'appendBuffer',
- value: function appendBuffer(bytes) {
- var _this2 = this;
- var error = undefined;
- var chunk = 512 * 1024;
- var i = 0;
- if (this.updating) {
- error = new Error('SourceBuffer.append() cannot be called ' + 'while an update is in progress');
- error.name = 'InvalidStateError';
- error.code = 11;
- throw error;
- }
- this.updating = true;
- this.mediaSource.readyState = 'open';
- this.trigger({ type: 'update' });
- // this is here to use recursion
- var chunkInData = function chunkInData() {
- _this2.segmentParser_.push(bytes.subarray(i, i + chunk));
- i += chunk;
- if (i < bytes.byteLength) {
- scheduleTick(chunkInData);
- } else {
- scheduleTick(_this2.segmentParser_.flush.bind(_this2.segmentParser_));
- }
- };
- chunkInData();
- }
- /**
- * Reset the parser and remove any data queued to be sent to the SWF.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
- */
- }, {
- key: 'abort',
- value: function abort() {
- this.buffer_ = [];
- this.bufferSize_ = 0;
- this.mediaSource.swfObj.vjs_abort();
- // report any outstanding updates have ended
- if (this.updating) {
- this.updating = false;
- this.trigger({ type: 'updateend' });
- }
- }
- /**
- * Flash cannot remove ranges already buffered in the NetStream
- * but seeking clears the buffer entirely. For most purposes,
- * having this operation act as a no-op is acceptable.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
- * @param {Double} start start of the section to remove
- * @param {Double} end end of the section to remove
- */
- }, {
- key: 'remove',
- value: function remove(start, end) {
- (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_);
- (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_);
- this.trigger({ type: 'update' });
- this.trigger({ type: 'updateend' });
- }
- /**
- * Receive a buffer from the flv.
- *
- * @param {Object} segment
- * @private
- */
- }, {
- key: 'receiveBuffer_',
- value: function receiveBuffer_(segment) {
- var _this3 = this;
- // create an in-band caption track if one is present in the segment
- (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource, segment);
- (0, _addTextTrackData2['default'])(this, segment.captions, segment.metadata);
- // Do this asynchronously since convertTagsToData_ can be time consuming
- scheduleTick(function () {
- var flvBytes = _this3.convertTagsToData_(segment);
- if (_this3.buffer_.length === 0) {
- scheduleTick(_this3.processBuffer_.bind(_this3));
- }
- if (flvBytes) {
- _this3.buffer_.push(flvBytes);
- _this3.bufferSize_ += flvBytes.byteLength;
- }
- });
- }
- /**
- * Append a portion of the current buffer to the SWF.
- *
- * @private
- */
- }, {
- key: 'processBuffer_',
- value: function processBuffer_() {
- var chunk = undefined;
- var i = undefined;
- var length = undefined;
- var binary = undefined;
- var b64str = undefined;
- var startByte = 0;
- var appendIterations = 0;
- var startTime = +new Date();
- var appendTime = undefined;
- if (!this.buffer_.length) {
- if (this.updating !== false) {
- this.updating = false;
- this.trigger({ type: 'updateend' });
- }
- // do nothing if the buffer is empty
- return;
- }
- do {
- appendIterations++;
- // concatenate appends up to the max append size
- chunk = this.buffer_[0].subarray(startByte, startByte + this.chunkSize_);
- // requeue any bytes that won't make it this round
- if (chunk.byteLength < this.chunkSize_ || this.buffer_[0].byteLength === startByte + this.chunkSize_) {
- startByte = 0;
- this.buffer_.shift();
- } else {
- startByte += this.chunkSize_;
- }
- this.bufferSize_ -= chunk.byteLength;
- // base64 encode the bytes
- binary = '';
- length = chunk.byteLength;
- for (i = 0; i < length; i++) {
- binary += String.fromCharCode(chunk[i]);
- }
- b64str = window.btoa(binary);
- // bypass normal ExternalInterface calls and pass xml directly
- // IE can be slow by default
- this.mediaSource.swfObj.CallFunction('<invoke name="vjs_appendBuffer"' + 'returntype="javascript"><arguments><string>' + b64str + '</string></arguments></invoke>');
- appendTime = new Date() - startTime;
- } while (this.buffer_.length && appendTime < _flashConstants2['default'].TIME_PER_TICK);
- if (this.buffer_.length && startByte) {
- this.buffer_[0] = this.buffer_[0].subarray(startByte);
- }
- if (appendTime >= _flashConstants2['default'].TIME_PER_TICK) {
- // We want to target 4 iterations per time-slot so that gives us
- // room to adjust to changes in Flash load and other externalities
- // such as garbage collection while still maximizing throughput
- this.chunkSize_ = Math.floor(this.chunkSize_ * (appendIterations / 4));
- }
- // We also make sure that the chunk-size doesn't drop below 1KB or
- // go above 1MB as a sanity check
- this.chunkSize_ = Math.max(_flashConstants2['default'].MIN_CHUNK, Math.min(this.chunkSize_, _flashConstants2['default'].MAX_CHUNK));
- // schedule another append if necessary
- if (this.bufferSize_ !== 0) {
- scheduleTick(this.processBuffer_.bind(this));
- } else {
- this.updating = false;
- this.trigger({ type: 'updateend' });
- }
- }
- /**
- * Turns an array of flv tags into a Uint8Array representing the
- * flv data. Also removes any tags that are before the current
- * time so that playback begins at or slightly after the right
- * place on a seek
- *
- * @private
- * @param {Object} segmentData object of segment data
- */
- }, {
- key: 'convertTagsToData_',
- value: function convertTagsToData_(segmentData) {
- var segmentByteLength = 0;
- var tech = this.mediaSource.tech_;
- var targetPts = 0;
- var i = undefined;
- var j = undefined;
- var segment = undefined;
- var filteredTags = [];
- var tags = this.getOrderedTags_(segmentData);
- // Establish the media timeline to PTS translation if we don't
- // have one already
- if (isNaN(this.basePtsOffset_) && tags.length) {
- this.basePtsOffset_ = tags[0].pts;
- }
- // Trim any tags that are before the end of the end of
- // the current buffer
- if (tech.buffered().length) {
- targetPts = tech.buffered().end(0) - this.timestampOffset;
- }
- // Trim to currentTime if it's ahead of buffered or buffered doesn't exist
- targetPts = Math.max(targetPts, tech.currentTime() - this.timestampOffset);
- // PTS values are represented in milliseconds
- targetPts *= 1e3;
- targetPts += this.basePtsOffset_;
- // skip tags with a presentation time less than the seek target
- for (i = 0; i < tags.length; i++) {
- if (tags[i].pts >= targetPts) {
- filteredTags.push(tags[i]);
- }
- }
- if (filteredTags.length === 0) {
- return;
- }
- // concatenate the bytes into a single segment
- for (i = 0; i < filteredTags.length; i++) {
- segmentByteLength += filteredTags[i].bytes.byteLength;
- }
- segment = new Uint8Array(segmentByteLength);
- for (i = 0, j = 0; i < filteredTags.length; i++) {
- segment.set(filteredTags[i].bytes, j);
- j += filteredTags[i].bytes.byteLength;
- }
- return segment;
- }
- /**
- * Assemble the FLV tags in decoder order.
- *
- * @private
- * @param {Object} segmentData object of segment data
- */
- }, {
- key: 'getOrderedTags_',
- value: function getOrderedTags_(segmentData) {
- var videoTags = segmentData.tags.videoTags;
- var audioTags = segmentData.tags.audioTags;
- var tag = undefined;
- var tags = [];
- while (videoTags.length || audioTags.length) {
- if (!videoTags.length) {
- // only audio tags remain
- tag = audioTags.shift();
- } else if (!audioTags.length) {
- // only video tags remain
- tag = videoTags.shift();
- } else if (audioTags[0].dts < videoTags[0].dts) {
- // audio should be decoded next
- tag = audioTags.shift();
- } else {
- // video should be decoded next
- tag = videoTags.shift();
- }
- tags.push(tag.finalize());
- }
- return tags;
- }
- }]);
- return FlashSourceBuffer;
- })(_videoJs2['default'].EventTarget);
- exports['default'] = FlashSourceBuffer;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./add-text-track-data":25,"./create-text-tracks-if-necessary":27,"./flash-constants":28,"./remove-cues-from-track":32,"mux.js":43}],31:[function(require,module,exports){
- (function (global){
- /**
- * @file html-media-source.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- var _virtualSourceBuffer = require('./virtual-source-buffer');
- var _virtualSourceBuffer2 = _interopRequireDefault(_virtualSourceBuffer);
- var _codecUtils = require('./codec-utils');
- /**
- * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
- * `avc1.<hhhhhh>`
- *
- * @param {Array} codecs an array of codec strings to fix
- * @return {Array} the translated codec array
- * @private
- */
- var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
- return codecs.map(function (codec) {
- return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
- var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
- var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
- return 'avc1.' + profileHex + '00' + avcLevelHex;
- });
- });
- };
- /**
- * Our MediaSource implementation in HTML, mimics native
- * MediaSource where/if possible.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
- * @class HtmlMediaSource
- * @extends videojs.EventTarget
- */
- var HtmlMediaSource = (function (_videojs$EventTarget) {
- _inherits(HtmlMediaSource, _videojs$EventTarget);
- function HtmlMediaSource() {
- var _this = this;
- _classCallCheck(this, HtmlMediaSource);
- _get(Object.getPrototypeOf(HtmlMediaSource.prototype), 'constructor', this).call(this);
- var property = undefined;
- this.nativeMediaSource_ = new window.MediaSource();
- // delegate to the native MediaSource's methods by default
- for (property in this.nativeMediaSource_) {
- if (!(property in HtmlMediaSource.prototype) && typeof this.nativeMediaSource_[property] === 'function') {
- this[property] = this.nativeMediaSource_[property].bind(this.nativeMediaSource_);
- }
- }
- // emulate `duration` and `seekable` until seeking can be
- // handled uniformly for live streams
- // see https://github.com/w3c/media-source/issues/5
- this.duration_ = NaN;
- Object.defineProperty(this, 'duration', {
- get: function get() {
- if (this.duration_ === Infinity) {
- return this.duration_;
- }
- return this.nativeMediaSource_.duration;
- },
- set: function set(duration) {
- this.duration_ = duration;
- if (duration !== Infinity) {
- this.nativeMediaSource_.duration = duration;
- return;
- }
- }
- });
- Object.defineProperty(this, 'seekable', {
- get: function get() {
- if (this.duration_ === Infinity) {
- return _videoJs2['default'].createTimeRanges([[0, this.nativeMediaSource_.duration]]);
- }
- return this.nativeMediaSource_.seekable;
- }
- });
- Object.defineProperty(this, 'readyState', {
- get: function get() {
- return this.nativeMediaSource_.readyState;
- }
- });
- Object.defineProperty(this, 'activeSourceBuffers', {
- get: function get() {
- return this.activeSourceBuffers_;
- }
- });
- // the list of virtual and native SourceBuffers created by this
- // MediaSource
- this.sourceBuffers = [];
- this.activeSourceBuffers_ = [];
- /**
- * update the list of active source buffers based upon various
- * imformation from HLS and video.js
- *
- * @private
- */
- this.updateActiveSourceBuffers_ = function () {
- // Retain the reference but empty the array
- _this.activeSourceBuffers_.length = 0;
- // By default, the audio in the combined virtual source buffer is enabled
- // and the audio-only source buffer (if it exists) is disabled.
- var combined = false;
- var audioOnly = true;
- // TODO: maybe we can store the sourcebuffers on the track objects?
- // safari may do something like this
- for (var i = 0; i < _this.player_.audioTracks().length; i++) {
- var track = _this.player_.audioTracks()[i];
- if (track.enabled && track.kind !== 'main') {
- // The enabled track is an alternate audio track so disable the audio in
- // the combined source buffer and enable the audio-only source buffer.
- combined = true;
- audioOnly = false;
- break;
- }
- }
- // Since we currently support a max of two source buffers, add all of the source
- // buffers (in order).
- _this.sourceBuffers.forEach(function (sourceBuffer) {
- /* eslinst-disable */
- // TODO once codecs are required, we can switch to using the codecs to determine
- // what stream is the video stream, rather than relying on videoTracks
- /* eslinst-enable */
- if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
- // combined
- sourceBuffer.audioDisabled_ = combined;
- } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
- // If the "combined" source buffer is video only, then we do not want
- // disable the audio-only source buffer (this is mostly for demuxed
- // audio and video hls)
- sourceBuffer.audioDisabled_ = true;
- audioOnly = false;
- } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
- // audio only
- sourceBuffer.audioDisabled_ = audioOnly;
- if (audioOnly) {
- return;
- }
- }
- _this.activeSourceBuffers_.push(sourceBuffer);
- });
- };
- // Re-emit MediaSource events on the polyfill
- ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
- this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
- }, this);
- // capture the associated player when the MediaSource is
- // successfully attached
- this.on('sourceopen', function (event) {
- // Get the player this MediaSource is attached to
- var video = document.querySelector('[src="' + _this.url_ + '"]');
- if (!video) {
- return;
- }
- _this.player_ = (0, _videoJs2['default'])(video.parentNode);
- if (_this.player_.audioTracks && _this.player_.audioTracks()) {
- _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
- _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
- _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
- }
- });
- // explicitly terminate any WebWorkers that were created
- // by SourceHandlers
- this.on('sourceclose', function (event) {
- this.sourceBuffers.forEach(function (sourceBuffer) {
- if (sourceBuffer.transmuxer_) {
- sourceBuffer.transmuxer_.terminate();
- }
- });
- this.sourceBuffers.length = 0;
- if (!this.player_) {
- return;
- }
- if (this.player_.audioTracks && this.player_.audioTracks()) {
- this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
- this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
- this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
- }
- });
- }
- /**
- * Add a range that that can now be seeked to.
- *
- * @param {Double} start where to start the addition
- * @param {Double} end where to end the addition
- * @private
- */
- _createClass(HtmlMediaSource, [{
- key: 'addSeekableRange_',
- value: function addSeekableRange_(start, end) {
- var error = undefined;
- if (this.duration !== Infinity) {
- error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
- error.name = 'InvalidStateError';
- error.code = 11;
- throw error;
- }
- if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
- this.nativeMediaSource_.duration = end;
- }
- }
- /**
- * Add a source buffer to the media source.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
- * @param {String} type the content-type of the content
- * @return {Object} the created source buffer
- */
- }, {
- key: 'addSourceBuffer',
- value: function addSourceBuffer(type) {
- var buffer = undefined;
- var parsedType = (0, _codecUtils.parseContentType)(type);
- // Create a VirtualSourceBuffer to transmux MPEG-2 transport
- // stream segments into fragmented MP4s
- if (parsedType.type === 'video/mp2t') {
- var codecs = [];
- if (parsedType.parameters && parsedType.parameters.codecs) {
- codecs = parsedType.parameters.codecs.split(',');
- codecs = translateLegacyCodecs(codecs);
- codecs = codecs.filter(function (codec) {
- return (0, _codecUtils.isAudioCodec)(codec) || (0, _codecUtils.isVideoCodec)(codec);
- });
- }
- if (codecs.length === 0) {
- codecs = ['avc1.4d400d', 'mp4a.40.2'];
- }
- buffer = new _virtualSourceBuffer2['default'](this, codecs);
- if (this.sourceBuffers.length !== 0) {
- // If another VirtualSourceBuffer already exists, then we are creating a
- // SourceBuffer for an alternate audio track and therefore we know that
- // the source has both an audio and video track.
- // That means we should trigger the manual creation of the real
- // SourceBuffers instead of waiting for the transmuxer to return data
- this.sourceBuffers[0].createRealSourceBuffers_();
- buffer.createRealSourceBuffers_();
- // Automatically disable the audio on the first source buffer if
- // a second source buffer is ever created
- this.sourceBuffers[0].audioDisabled_ = true;
- }
- } else {
- // delegate to the native implementation
- buffer = this.nativeMediaSource_.addSourceBuffer(type);
- }
- this.sourceBuffers.push(buffer);
- return buffer;
- }
- }]);
- return HtmlMediaSource;
- })(_videoJs2['default'].EventTarget);
- exports['default'] = HtmlMediaSource;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./codec-utils":26,"./virtual-source-buffer":35}],32:[function(require,module,exports){
- /**
- * @file remove-cues-from-track.js
- */
- /**
- * Remove cues from a track on video.js.
- *
- * @param {Double} start start of where we should remove the cue
- * @param {Double} end end of where the we should remove the cue
- * @param {Object} track the text track to remove the cues from
- * @private
- */
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
- var i = undefined;
- var cue = undefined;
- if (!track) {
- return;
- }
- i = track.cues.length;
- while (i--) {
- cue = track.cues[i];
- // Remove any overlapping cue
- if (cue.startTime <= end && cue.endTime >= start) {
- track.removeCue(cue);
- }
- }
- };
- exports["default"] = removeCuesFromTrack;
- module.exports = exports["default"];
- },{}],33:[function(require,module,exports){
- /**
- * @file transmuxer-worker.js
- */
- /**
- * videojs-contrib-media-sources
- *
- * Copyright (c) 2015 Brightcove
- * All rights reserved.
- *
- * Handles communication between the browser-world and the mux.js
- * transmuxer running inside of a WebWorker by exposing a simple
- * message-based interface to a Transmuxer object.
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- var _muxJs = require('mux.js');
- var _muxJs2 = _interopRequireDefault(_muxJs);
- /**
- * Re-emits tranmsuxer events by converting them into messages to the
- * world outside the worker.
- *
- * @param {Object} transmuxer the transmuxer to wire events on
- * @private
- */
- var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) {
- transmuxer.on('data', function (segment) {
- // transfer ownership of the underlying ArrayBuffer
- // instead of doing a copy to save memory
- // ArrayBuffers are transferable but generic TypedArrays are not
- // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
- var typedArray = segment.data;
- segment.data = typedArray.buffer;
- postMessage({
- action: 'data',
- segment: segment,
- byteOffset: typedArray.byteOffset,
- byteLength: typedArray.byteLength
- }, [segment.data]);
- });
- if (transmuxer.captionStream) {
- transmuxer.captionStream.on('data', function (caption) {
- postMessage({
- action: 'caption',
- data: caption
- });
- });
- }
- transmuxer.on('done', function (data) {
- postMessage({ action: 'done' });
- });
- };
- /**
- * All incoming messages route through this hash. If no function exists
- * to handle an incoming message, then we ignore the message.
- *
- * @class MessageHandlers
- * @param {Object} options the options to initialize with
- */
- var MessageHandlers = (function () {
- function MessageHandlers(options) {
- _classCallCheck(this, MessageHandlers);
- this.options = options || {};
- this.init();
- }
- /**
- * Our web wroker interface so that things can talk to mux.js
- * that will be running in a web worker. the scope is passed to this by
- * webworkify.
- *
- * @param {Object} self the scope for the web worker
- */
- /**
- * initialize our web worker and wire all the events.
- */
- _createClass(MessageHandlers, [{
- key: 'init',
- value: function init() {
- if (this.transmuxer) {
- this.transmuxer.dispose();
- }
- this.transmuxer = new _muxJs2['default'].mp4.Transmuxer(this.options);
- wireTransmuxerEvents(this.transmuxer);
- }
- /**
- * Adds data (a ts segment) to the start of the transmuxer pipeline for
- * processing.
- *
- * @param {ArrayBuffer} data data to push into the muxer
- */
- }, {
- key: 'push',
- value: function push(data) {
- // Cast array buffer to correct type for transmuxer
- var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
- this.transmuxer.push(segment);
- }
- /**
- * Recreate the transmuxer so that the next segment added via `push`
- * start with a fresh transmuxer.
- */
- }, {
- key: 'reset',
- value: function reset() {
- this.init();
- }
- /**
- * Set the value that will be used as the `baseMediaDecodeTime` time for the
- * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
- * set relative to the first based on the PTS values.
- *
- * @param {Object} data used to set the timestamp offset in the muxer
- */
- }, {
- key: 'setTimestampOffset',
- value: function setTimestampOffset(data) {
- var timestampOffset = data.timestampOffset || 0;
- this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
- }
- /**
- * Forces the pipeline to finish processing the last segment and emit it's
- * results.
- *
- * @param {Object} data event data, not really used
- */
- }, {
- key: 'flush',
- value: function flush(data) {
- this.transmuxer.flush();
- }
- }]);
- return MessageHandlers;
- })();
- var Worker = function Worker(self) {
- self.onmessage = function (event) {
- if (event.data.action === 'init' && event.data.options) {
- this.messageHandlers = new MessageHandlers(event.data.options);
- return;
- }
- if (!this.messageHandlers) {
- this.messageHandlers = new MessageHandlers();
- }
- if (event.data && event.data.action && event.data.action !== 'init') {
- if (this.messageHandlers[event.data.action]) {
- this.messageHandlers[event.data.action](event.data);
- }
- }
- };
- };
- exports['default'] = function (self) {
- return new Worker(self);
- };
- module.exports = exports['default'];
- },{"mux.js":43}],34:[function(require,module,exports){
- (function (global){
- /**
- * @file videojs-contrib-media-sources.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _flashMediaSource = require('./flash-media-source');
- var _flashMediaSource2 = _interopRequireDefault(_flashMediaSource);
- var _htmlMediaSource = require('./html-media-source');
- var _htmlMediaSource2 = _interopRequireDefault(_htmlMediaSource);
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- var urlCount = 0;
- // ------------
- // Media Source
- // ------------
- var defaults = {
- // how to determine the MediaSource implementation to use. There
- // are three available modes:
- // - auto: use native MediaSources where available and Flash
- // everywhere else
- // - html5: always use native MediaSources
- // - flash: always use the Flash MediaSource polyfill
- mode: 'auto'
- };
- // store references to the media sources so they can be connected
- // to a video element (a swf object)
- // TODO: can we store this somewhere local to this module?
- _videoJs2['default'].mediaSources = {};
- /**
- * Provide a method for a swf object to notify JS that a
- * media source is now open.
- *
- * @param {String} msObjectURL string referencing the MSE Object URL
- * @param {String} swfId the swf id
- */
- var open = function open(msObjectURL, swfId) {
- var mediaSource = _videoJs2['default'].mediaSources[msObjectURL];
- if (mediaSource) {
- mediaSource.trigger({ type: 'sourceopen', swfId: swfId });
- } else {
- throw new Error('Media Source not found (Video.js)');
- }
- };
- /**
- * Check to see if the native MediaSource object exists and supports
- * an MP4 container with both H.264 video and AAC-LC audio.
- *
- * @return {Boolean} if native media sources are supported
- */
- var supportsNativeMediaSources = function supportsNativeMediaSources() {
- return !!window.MediaSource && !!window.MediaSource.isTypeSupported && window.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
- };
- /**
- * An emulation of the MediaSource API so that we can support
- * native and non-native functionality such as flash and
- * video/mp2t videos. returns an instance of HtmlMediaSource or
- * FlashMediaSource depending on what is supported and what options
- * are passed in.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
- * @param {Object} options options to use during setup.
- */
- var MediaSource = function MediaSource(options) {
- var settings = _videoJs2['default'].mergeOptions(defaults, options);
- this.MediaSource = {
- open: open,
- supportsNativeMediaSources: supportsNativeMediaSources
- };
- // determine whether HTML MediaSources should be used
- if (settings.mode === 'html5' || settings.mode === 'auto' && supportsNativeMediaSources()) {
- return new _htmlMediaSource2['default']();
- }
- // otherwise, emulate them through the SWF
- return new _flashMediaSource2['default']();
- };
- exports.MediaSource = MediaSource;
- MediaSource.open = open;
- MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
- /**
- * A wrapper around the native URL for our MSE object
- * implementation, this object is exposed under videojs.URL
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
- */
- var URL = {
- /**
- * A wrapper around the native createObjectURL for our objects.
- * This function maps a native or emulated mediaSource to a blob
- * url so that it can be loaded into video.js
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
- * @param {MediaSource} object the object to create a blob url to
- */
- createObjectURL: function createObjectURL(object) {
- var objectUrlPrefix = 'blob:vjs-media-source/';
- var url = undefined;
- // use the native MediaSource to generate an object URL
- if (object instanceof _htmlMediaSource2['default']) {
- url = window.URL.createObjectURL(object.nativeMediaSource_);
- object.url_ = url;
- return url;
- }
- // if the object isn't an emulated MediaSource, delegate to the
- // native implementation
- if (!(object instanceof _flashMediaSource2['default'])) {
- url = window.URL.createObjectURL(object);
- object.url_ = url;
- return url;
- }
- // build a URL that can be used to map back to the emulated
- // MediaSource
- url = objectUrlPrefix + urlCount;
- urlCount++;
- // setup the mapping back to object
- _videoJs2['default'].mediaSources[url] = object;
- return url;
- }
- };
- exports.URL = URL;
- _videoJs2['default'].MediaSource = MediaSource;
- _videoJs2['default'].URL = URL;
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./flash-media-source":29,"./html-media-source":31}],35:[function(require,module,exports){
- (function (global){
- /**
- * @file virtual-source-buffer.js
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary');
- var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary);
- var _removeCuesFromTrack = require('./remove-cues-from-track');
- var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack);
- var _addTextTrackData = require('./add-text-track-data');
- var _addTextTrackData2 = _interopRequireDefault(_addTextTrackData);
- var _webworkify = require('webworkify');
- var _webworkify2 = _interopRequireDefault(_webworkify);
- var _transmuxerWorker = require('./transmuxer-worker');
- var _transmuxerWorker2 = _interopRequireDefault(_transmuxerWorker);
- var _codecUtils = require('./codec-utils');
- /**
- * VirtualSourceBuffers exist so that we can transmux non native formats
- * into a native format, but keep the same api as a native source buffer.
- * It creates a transmuxer, that works in its own thread (a web worker) and
- * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
- * then send all of that data to the naive sourcebuffer so that it is
- * indestinguishable from a natively supported format.
- *
- * @param {HtmlMediaSource} mediaSource the parent mediaSource
- * @param {Array} codecs array of codecs that we will be dealing with
- * @class VirtualSourceBuffer
- * @extends video.js.EventTarget
- */
- var VirtualSourceBuffer = (function (_videojs$EventTarget) {
- _inherits(VirtualSourceBuffer, _videojs$EventTarget);
- function VirtualSourceBuffer(mediaSource, codecs) {
- var _this = this;
- _classCallCheck(this, VirtualSourceBuffer);
- _get(Object.getPrototypeOf(VirtualSourceBuffer.prototype), 'constructor', this).call(this, _videoJs2['default'].EventTarget);
- this.timestampOffset_ = 0;
- this.pendingBuffers_ = [];
- this.bufferUpdating_ = false;
- this.mediaSource_ = mediaSource;
- this.codecs_ = codecs;
- this.audioCodec_ = null;
- this.videoCodec_ = null;
- this.audioDisabled_ = false;
- var options = {
- remux: false
- };
- this.codecs_.forEach(function (codec) {
- if ((0, _codecUtils.isAudioCodec)(codec)) {
- _this.audioCodec_ = codec;
- } else if ((0, _codecUtils.isVideoCodec)(codec)) {
- _this.videoCodec_ = codec;
- }
- });
- // append muxed segments to their respective native buffers as
- // soon as they are available
- this.transmuxer_ = (0, _webworkify2['default'])(_transmuxerWorker2['default']);
- this.transmuxer_.postMessage({ action: 'init', options: options });
- this.transmuxer_.onmessage = function (event) {
- if (event.data.action === 'data') {
- return _this.data_(event);
- }
- if (event.data.action === 'done') {
- return _this.done_(event);
- }
- };
- // this timestampOffset is a property with the side-effect of resetting
- // baseMediaDecodeTime in the transmuxer on the setter
- Object.defineProperty(this, 'timestampOffset', {
- get: function get() {
- return this.timestampOffset_;
- },
- set: function set(val) {
- if (typeof val === 'number' && val >= 0) {
- this.timestampOffset_ = val;
- // We have to tell the transmuxer to set the baseMediaDecodeTime to
- // the desired timestampOffset for the next segment
- this.transmuxer_.postMessage({
- action: 'setTimestampOffset',
- timestampOffset: val
- });
- }
- }
- });
- // setting the append window affects both source buffers
- Object.defineProperty(this, 'appendWindowStart', {
- get: function get() {
- return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
- },
- set: function set(start) {
- if (this.videoBuffer_) {
- this.videoBuffer_.appendWindowStart = start;
- }
- if (this.audioBuffer_) {
- this.audioBuffer_.appendWindowStart = start;
- }
- }
- });
- // this buffer is "updating" if either of its native buffers are
- Object.defineProperty(this, 'updating', {
- get: function get() {
- return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
- }
- });
- // the buffered property is the intersection of the buffered
- // ranges of the native source buffers
- Object.defineProperty(this, 'buffered', {
- get: function get() {
- var start = null;
- var end = null;
- var arity = 0;
- var extents = [];
- var ranges = [];
- if (!this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
- return _videoJs2['default'].createTimeRange();
- }
- // Handle the case where we only have one buffer
- if (!this.videoBuffer_) {
- return this.audioBuffer_.buffered;
- } else if (this.audioDisabled_ || !this.audioBuffer_) {
- return this.videoBuffer_.buffered;
- }
- // Handle the case where there is no buffer data
- if ((!this.videoBuffer_ || this.videoBuffer_.buffered.length === 0) && (!this.audioBuffer_ || this.audioBuffer_.buffered.length === 0)) {
- return _videoJs2['default'].createTimeRange();
- }
- // Handle the case where we have both buffers and create an
- // intersection of the two
- var videoBuffered = this.videoBuffer_.buffered;
- var audioBuffered = this.audioBuffer_.buffered;
- var count = videoBuffered.length;
- // A) Gather up all start and end times
- while (count--) {
- extents.push({ time: videoBuffered.start(count), type: 'start' });
- extents.push({ time: videoBuffered.end(count), type: 'end' });
- }
- count = audioBuffered.length;
- while (count--) {
- extents.push({ time: audioBuffered.start(count), type: 'start' });
- extents.push({ time: audioBuffered.end(count), type: 'end' });
- }
- // B) Sort them by time
- extents.sort(function (a, b) {
- return a.time - b.time;
- });
- // C) Go along one by one incrementing arity for start and decrementing
- // arity for ends
- for (count = 0; count < extents.length; count++) {
- if (extents[count].type === 'start') {
- arity++;
- // D) If arity is ever incremented to 2 we are entering an
- // overlapping range
- if (arity === 2) {
- start = extents[count].time;
- }
- } else if (extents[count].type === 'end') {
- arity--;
- // E) If arity is ever decremented to 1 we leaving an
- // overlapping range
- if (arity === 1) {
- end = extents[count].time;
- }
- }
- // F) Record overlapping ranges
- if (start !== null && end !== null) {
- ranges.push([start, end]);
- start = null;
- end = null;
- }
- }
- return _videoJs2['default'].createTimeRanges(ranges);
- }
- });
- }
- /**
- * When we get a data event from the transmuxer
- * we call this function and handle the data that
- * was sent to us
- *
- * @private
- * @param {Event} event the data event from the transmuxer
- */
- _createClass(VirtualSourceBuffer, [{
- key: 'data_',
- value: function data_(event) {
- var segment = event.data.segment;
- // Cast ArrayBuffer to TypedArray
- segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
- (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment);
- // Add the segments to the pendingBuffers array
- this.pendingBuffers_.push(segment);
- return;
- }
- /**
- * When we get a done event from the transmuxer
- * we call this function and we process all
- * of the pending data that we have been saving in the
- * data_ function
- *
- * @private
- * @param {Event} event the done event from the transmuxer
- */
- }, {
- key: 'done_',
- value: function done_(event) {
- // All buffers should have been flushed from the muxer
- // start processing anything we have received
- this.processPendingSegments_();
- return;
- }
- /**
- * Create our internal native audio/video source buffers and add
- * event handlers to them with the following conditions:
- * 1. they do not already exist on the mediaSource
- * 2. this VSB has a codec for them
- *
- * @private
- */
- }, {
- key: 'createRealSourceBuffers_',
- value: function createRealSourceBuffers_() {
- var _this2 = this;
- var types = ['audio', 'video'];
- types.forEach(function (type) {
- // Don't create a SourceBuffer of this type if we don't have a
- // codec for it
- if (!_this2[type + 'Codec_']) {
- return;
- }
- // Do nothing if a SourceBuffer of this type already exists
- if (_this2[type + 'Buffer_']) {
- return;
- }
- var buffer = null;
- // If the mediasource already has a SourceBuffer for the codec
- // use that
- if (_this2.mediaSource_[type + 'Buffer_']) {
- buffer = _this2.mediaSource_[type + 'Buffer_'];
- } else {
- buffer = _this2.mediaSource_.nativeMediaSource_.addSourceBuffer(type + '/mp4;codecs="' + _this2[type + 'Codec_'] + '"');
- _this2.mediaSource_[type + 'Buffer_'] = buffer;
- }
- _this2[type + 'Buffer_'] = buffer;
- // Wire up the events to the SourceBuffer
- ['update', 'updatestart', 'updateend'].forEach(function (event) {
- buffer.addEventListener(event, function () {
- // if audio is disabled
- if (type === 'audio' && _this2.audioDisabled_) {
- return;
- }
- var shouldTrigger = types.every(function (t) {
- // skip checking audio's updating status if audio
- // is not enabled
- if (t === 'audio' && _this2.audioDisabled_) {
- return true;
- }
- // if the other type if updating we don't trigger
- if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
- return false;
- }
- return true;
- });
- if (shouldTrigger) {
- return _this2.trigger(event);
- }
- });
- });
- });
- }
- /**
- * Emulate the native mediasource function, but our function will
- * send all of the proposed segments to the transmuxer so that we
- * can transmux them before we append them to our internal
- * native source buffers in the correct format.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
- * @param {Uint8Array} segment the segment to append to the buffer
- */
- }, {
- key: 'appendBuffer',
- value: function appendBuffer(segment) {
- // Start the internal "updating" state
- this.bufferUpdating_ = true;
- this.transmuxer_.postMessage({
- action: 'push',
- // Send the typed-array of data as an ArrayBuffer so that
- // it can be sent as a "Transferable" and avoid the costly
- // memory copy
- data: segment.buffer,
- // To recreate the original typed-array, we need information
- // about what portion of the ArrayBuffer it was a view into
- byteOffset: segment.byteOffset,
- byteLength: segment.byteLength
- }, [segment.buffer]);
- this.transmuxer_.postMessage({ action: 'flush' });
- }
- /**
- * Emulate the native mediasource function and remove parts
- * of the buffer from any of our internal buffers that exist
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
- * @param {Double} start position to start the remove at
- * @param {Double} end position to end the remove at
- */
- }, {
- key: 'remove',
- value: function remove(start, end) {
- if (this.videoBuffer_) {
- this.videoBuffer_.remove(start, end);
- }
- if (!this.audioDisabled_ && this.audioBuffer_) {
- this.audioBuffer_.remove(start, end);
- }
- // Remove Metadata Cues (id3)
- (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_);
- // Remove Any Captions
- (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_);
- }
- /**
- * Process any segments that the muxer has output
- * Concatenate segments together based on type and append them into
- * their respective sourceBuffers
- *
- * @private
- */
- }, {
- key: 'processPendingSegments_',
- value: function processPendingSegments_() {
- var sortedSegments = {
- video: {
- segments: [],
- bytes: 0
- },
- audio: {
- segments: [],
- bytes: 0
- },
- captions: [],
- metadata: []
- };
- // Sort segments into separate video/audio arrays and
- // keep track of their total byte lengths
- sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
- var type = segment.type;
- var data = segment.data;
- segmentObj[type].segments.push(data);
- segmentObj[type].bytes += data.byteLength;
- // Gather any captions into a single array
- if (segment.captions) {
- segmentObj.captions = segmentObj.captions.concat(segment.captions);
- }
- if (segment.info) {
- segmentObj[type].info = segment.info;
- }
- // Gather any metadata into a single array
- if (segment.metadata) {
- segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
- }
- return segmentObj;
- }, sortedSegments);
- // Create the real source buffers if they don't exist by now since we
- // finally are sure what tracks are contained in the source
- if (!this.videoBuffer_ && !this.audioBuffer_) {
- // Remove any codecs that may have been specified by default but
- // are no longer applicable now
- if (sortedSegments.video.bytes === 0) {
- this.videoCodec_ = null;
- }
- if (sortedSegments.audio.bytes === 0) {
- this.audioCodec_ = null;
- }
- this.createRealSourceBuffers_();
- }
- if (sortedSegments.audio.info) {
- this.mediaSource_.trigger({ type: 'audioinfo', info: sortedSegments.audio.info });
- }
- if (sortedSegments.video.info) {
- this.mediaSource_.trigger({ type: 'videoinfo', info: sortedSegments.video.info });
- }
- // Merge multiple video and audio segments into one and append
- if (this.videoBuffer_) {
- this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
- // TODO: are video tracks the only ones with text tracks?
- (0, _addTextTrackData2['default'])(this, sortedSegments.captions, sortedSegments.metadata);
- }
- if (!this.audioDisabled_ && this.audioBuffer_) {
- this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
- }
- this.pendingBuffers_.length = 0;
- // We are no longer in the internal "updating" state
- this.bufferUpdating_ = false;
- }
- /**
- * Combine all segments into a single Uint8Array and then append them
- * to the destination buffer
- *
- * @param {Object} segmentObj
- * @param {SourceBuffer} destinationBuffer native source buffer to append data to
- * @private
- */
- }, {
- key: 'concatAndAppendSegments_',
- value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
- var offset = 0;
- var tempBuffer = undefined;
- if (segmentObj.bytes) {
- tempBuffer = new Uint8Array(segmentObj.bytes);
- // Combine the individual segments into one large typed-array
- segmentObj.segments.forEach(function (segment) {
- tempBuffer.set(segment, offset);
- offset += segment.byteLength;
- });
- destinationBuffer.appendBuffer(tempBuffer);
- }
- }
- /**
- * Emulate the native mediasource function. abort any soureBuffer
- * actions and throw out any un-appended data.
- *
- * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
- */
- }, {
- key: 'abort',
- value: function abort() {
- if (this.videoBuffer_) {
- this.videoBuffer_.abort();
- }
- if (this.audioBuffer_) {
- this.audioBuffer_.abort();
- }
- if (this.transmuxer_) {
- this.transmuxer_.postMessage({ action: 'reset' });
- }
- this.pendingBuffers_.length = 0;
- this.bufferUpdating_ = false;
- }
- }]);
- return VirtualSourceBuffer;
- })(_videoJs2['default'].EventTarget);
- exports['default'] = VirtualSourceBuffer;
- module.exports = exports['default'];
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./add-text-track-data":25,"./codec-utils":26,"./create-text-tracks-if-necessary":27,"./remove-cues-from-track":32,"./transmuxer-worker":33,"webworkify":56}],36:[function(require,module,exports){
- /**
- * mux.js
- *
- * Copyright (c) 2016 Brightcove
- * All rights reserved.
- *
- * A stream-based aac to mp4 converter. This utility can be used to
- * deliver mp4s to a SourceBuffer on platforms that support native
- * Media Source Extensions.
- */
- 'use strict';
- var Stream = require('../utils/stream.js');
- // Constants
- var AacStream;
- /**
- * Splits an incoming stream of binary data into ADTS and ID3 Frames.
- */
- AacStream = function() {
- var
- everything = new Uint8Array(),
- receivedTimeStamp = false,
- timeStamp = 0;
- AacStream.prototype.init.call(this);
- this.setTimestamp = function (timestamp) {
- timeStamp = timestamp;
- };
- this.parseId3TagSize = function(header, byteIndex) {
- var
- returnSize = (header[byteIndex + 6] << 21) |
- (header[byteIndex + 7] << 14) |
- (header[byteIndex + 8] << 7) |
- (header[byteIndex + 9]),
- flags = header[byteIndex + 5],
- footerPresent = (flags & 16) >> 4;
- if (footerPresent) {
- return returnSize + 20;
- }
- return returnSize + 10;
- };
- this.parseAdtsSize = function(header, byteIndex) {
- var
- lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
- middle = header[byteIndex + 4] << 3,
- highTwo = header[byteIndex + 3] & 0x3 << 11;
- return (highTwo | middle) | lowThree;
- };
- this.push = function(bytes) {
- var
- frameSize = 0,
- byteIndex = 0,
- bytesLeft,
- chunk,
- packet,
- tempLength;
- // If there are bytes remaining from the last segment, prepend them to the
- // bytes that were pushed in
- if (everything.length) {
- tempLength = everything.length;
- everything = new Uint8Array(bytes.byteLength + tempLength);
- everything.set(everything.subarray(0, tempLength));
- everything.set(bytes, tempLength);
- } else {
- everything = bytes;
- }
- while (everything.length - byteIndex >= 3) {
- if ((everything[byteIndex] === 'I'.charCodeAt(0)) &&
- (everything[byteIndex + 1] === 'D'.charCodeAt(0)) &&
- (everything[byteIndex + 2] === '3'.charCodeAt(0))) {
- // Exit early because we don't have enough to parse
- // the ID3 tag header
- if (everything.length - byteIndex < 10) {
- break;
- }
- // check framesize
- frameSize = this.parseId3TagSize(everything, byteIndex);
- // Exit early if we don't have enough in the buffer
- // to emit a full packet
- if (frameSize > everything.length) {
- break;
- }
- chunk = {
- type: 'timed-metadata',
- data: everything.subarray(byteIndex, byteIndex + frameSize)
- };
- this.trigger('data', chunk);
- byteIndex += frameSize;
- continue;
- } else if ((everything[byteIndex] & 0xff === 0xff) &&
- ((everything[byteIndex + 1] & 0xf0) === 0xf0)) {
- // Exit early because we don't have enough to parse
- // the ADTS frame header
- if (everything.length - byteIndex < 7) {
- break;
- }
- frameSize = this.parseAdtsSize(everything, byteIndex);
- // Exit early if we don't have enough in the buffer
- // to emit a full packet
- if (frameSize > everything.length) {
- break;
- }
- packet = {
- type: 'audio',
- data: everything.subarray(byteIndex, byteIndex + frameSize),
- pts: timeStamp,
- dts: timeStamp,
- };
- this.trigger('data', packet);
- byteIndex += frameSize;
- continue;
- }
- byteIndex++;
- }
- bytesLeft = everything.length - byteIndex;
- if (bytesLeft > 0) {
- everything = everything.subarray(byteIndex);
- } else {
- everything = new Uint8Array();
- }
- };
- };
- AacStream.prototype = new Stream();
- module.exports = AacStream;
- },{"../utils/stream.js":55}],37:[function(require,module,exports){
- 'use strict';
- var Stream = require('../utils/stream.js');
- var AdtsStream;
- var
- ADTS_SAMPLING_FREQUENCIES = [
- 96000,
- 88200,
- 64000,
- 48000,
- 44100,
- 32000,
- 24000,
- 22050,
- 16000,
- 12000,
- 11025,
- 8000,
- 7350
- ];
- /*
- * Accepts a ElementaryStream and emits data events with parsed
- * AAC Audio Frames of the individual packets. Input audio in ADTS
- * format is unpacked and re-emitted as AAC frames.
- *
- * @see http://wiki.multimedia.cx/index.php?title=ADTS
- * @see http://wiki.multimedia.cx/?title=Understanding_AAC
- */
- AdtsStream = function() {
- var self, buffer;
- AdtsStream.prototype.init.call(this);
- self = this;
- this.push = function(packet) {
- var
- i = 0,
- frameNum = 0,
- frameLength,
- protectionSkipBytes,
- frameEnd,
- oldBuffer,
- numFrames,
- sampleCount,
- adtsFrameDuration;
- if (packet.type !== 'audio') {
- // ignore non-audio data
- return;
- }
- // Prepend any data in the buffer to the input data so that we can parse
- // aac frames the cross a PES packet boundary
- if (buffer) {
- oldBuffer = buffer;
- buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
- buffer.set(oldBuffer);
- buffer.set(packet.data, oldBuffer.byteLength);
- } else {
- buffer = packet.data;
- }
- // unpack any ADTS frames which have been fully received
- // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
- while (i + 5 < buffer.length) {
- // Loook for the start of an ADTS header..
- if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
- // If a valid header was not found, jump one forward and attempt to
- // find a valid ADTS header starting at the next byte
- i++;
- continue;
- }
- // The protection skip bit tells us if we have 2 bytes of CRC data at the
- // end of the ADTS header
- protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2;
- // Frame length is a 13 bit integer starting 16 bits from the
- // end of the sync sequence
- frameLength = ((buffer[i + 3] & 0x03) << 11) |
- (buffer[i + 4] << 3) |
- ((buffer[i + 5] & 0xe0) >> 5);
- sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
- adtsFrameDuration = (sampleCount * 90000) /
- ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
- frameEnd = i + frameLength;
- // If we don't have enough data to actually finish this ADTS frame, return
- // and wait for more data
- if (buffer.byteLength < frameEnd) {
- return;
- }
- // Otherwise, deliver the complete AAC frame
- this.trigger('data', {
- pts: packet.pts + (frameNum * adtsFrameDuration),
- dts: packet.dts + (frameNum * adtsFrameDuration),
- sampleCount: sampleCount,
- audioobjecttype: ((buffer[i + 2] >>> 6) & 0x03) + 1,
- channelcount: ((buffer[i + 2] & 1) << 2) |
- ((buffer[i + 3] & 0xc0) >>> 6),
- samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
- samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
- // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
- samplesize: 16,
- data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
- });
- // If the buffer is empty, clear it and return
- if (buffer.byteLength === frameEnd) {
- buffer = undefined;
- return;
- }
- frameNum++;
- // Remove the finished frame from the buffer and start the process again
- buffer = buffer.subarray(frameEnd);
- }
- };
- this.flush = function() {
- this.trigger('done');
- };
- };
- AdtsStream.prototype = new Stream();
- module.exports = AdtsStream;
- },{"../utils/stream.js":55}],38:[function(require,module,exports){
- 'use strict';
- var Stream = require('../utils/stream.js');
- var ExpGolomb = require('../utils/exp-golomb.js');
- var H264Stream, NalByteStream;
- /**
- * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
- */
- NalByteStream = function() {
- var
- syncPoint = 0,
- i,
- buffer;
- NalByteStream.prototype.init.call(this);
- this.push = function(data) {
- var swapBuffer;
- if (!buffer) {
- buffer = data.data;
- } else {
- swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
- swapBuffer.set(buffer);
- swapBuffer.set(data.data, buffer.byteLength);
- buffer = swapBuffer;
- }
- // Rec. ITU-T H.264, Annex B
- // scan for NAL unit boundaries
- // a match looks like this:
- // 0 0 1 .. NAL .. 0 0 1
- // ^ sync point ^ i
- // or this:
- // 0 0 1 .. NAL .. 0 0 0
- // ^ sync point ^ i
- // advance the sync point to a NAL start, if necessary
- for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
- if (buffer[syncPoint + 2] === 1) {
- // the sync point is properly aligned
- i = syncPoint + 5;
- break;
- }
- }
- while (i < buffer.byteLength) {
- // look at the current byte to determine if we've hit the end of
- // a NAL unit boundary
- switch (buffer[i]) {
- case 0:
- // skip past non-sync sequences
- if (buffer[i - 1] !== 0) {
- i += 2;
- break;
- } else if (buffer[i - 2] !== 0) {
- i++;
- break;
- }
- // deliver the NAL unit if it isn't empty
- if (syncPoint + 3 !== i - 2) {
- this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
- }
- // drop trailing zeroes
- do {
- i++;
- } while (buffer[i] !== 1 && i < buffer.length);
- syncPoint = i - 2;
- i += 3;
- break;
- case 1:
- // skip past non-sync sequences
- if (buffer[i - 1] !== 0 ||
- buffer[i - 2] !== 0) {
- i += 3;
- break;
- }
- // deliver the NAL unit
- this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
- syncPoint = i - 2;
- i += 3;
- break;
- default:
- // the current byte isn't a one or zero, so it cannot be part
- // of a sync sequence
- i += 3;
- break;
- }
- }
- // filter out the NAL units that were delivered
- buffer = buffer.subarray(syncPoint);
- i -= syncPoint;
- syncPoint = 0;
- };
- this.flush = function() {
- // deliver the last buffered NAL unit
- if (buffer && buffer.byteLength > 3) {
- this.trigger('data', buffer.subarray(syncPoint + 3));
- }
- // reset the stream state
- buffer = null;
- syncPoint = 0;
- this.trigger('done');
- };
- };
- NalByteStream.prototype = new Stream();
- /**
- * Accepts input from a ElementaryStream and produces H.264 NAL unit data
- * events.
- */
- H264Stream = function() {
- var
- nalByteStream = new NalByteStream(),
- self,
- trackId,
- currentPts,
- currentDts,
- discardEmulationPreventionBytes,
- readSequenceParameterSet,
- skipScalingList;
- H264Stream.prototype.init.call(this);
- self = this;
- this.push = function(packet) {
- if (packet.type !== 'video') {
- return;
- }
- trackId = packet.trackId;
- currentPts = packet.pts;
- currentDts = packet.dts;
- nalByteStream.push(packet);
- };
- nalByteStream.on('data', function(data) {
- var
- event = {
- trackId: trackId,
- pts: currentPts,
- dts: currentDts,
- data: data
- };
- switch (data[0] & 0x1f) {
- case 0x05:
- event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
- break;
- case 0x06:
- event.nalUnitType = 'sei_rbsp';
- event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
- break;
- case 0x07:
- event.nalUnitType = 'seq_parameter_set_rbsp';
- event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
- event.config = readSequenceParameterSet(event.escapedRBSP);
- break;
- case 0x08:
- event.nalUnitType = 'pic_parameter_set_rbsp';
- break;
- case 0x09:
- event.nalUnitType = 'access_unit_delimiter_rbsp';
- break;
- default:
- break;
- }
- self.trigger('data', event);
- });
- nalByteStream.on('done', function() {
- self.trigger('done');
- });
- this.flush = function() {
- nalByteStream.flush();
- };
- /**
- * Advance the ExpGolomb decoder past a scaling list. The scaling
- * list is optionally transmitted as part of a sequence parameter
- * set and is not relevant to transmuxing.
- * @param count {number} the number of entries in this scaling list
- * @param expGolombDecoder {object} an ExpGolomb pointed to the
- * start of a scaling list
- * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
- */
- skipScalingList = function(count, expGolombDecoder) {
- var
- lastScale = 8,
- nextScale = 8,
- j,
- deltaScale;
- for (j = 0; j < count; j++) {
- if (nextScale !== 0) {
- deltaScale = expGolombDecoder.readExpGolomb();
- nextScale = (lastScale + deltaScale + 256) % 256;
- }
- lastScale = (nextScale === 0) ? lastScale : nextScale;
- }
- };
- /**
- * Expunge any "Emulation Prevention" bytes from a "Raw Byte
- * Sequence Payload"
- * @param data {Uint8Array} the bytes of a RBSP from a NAL
- * unit
- * @return {Uint8Array} the RBSP without any Emulation
- * Prevention Bytes
- */
- discardEmulationPreventionBytes = function(data) {
- var
- length = data.byteLength,
- emulationPreventionBytesPositions = [],
- i = 1,
- newLength, newData;
- // Find all `Emulation Prevention Bytes`
- while (i < length - 2) {
- if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
- emulationPreventionBytesPositions.push(i + 2);
- i += 2;
- } else {
- i++;
- }
- }
- // If no Emulation Prevention Bytes were found just return the original
- // array
- if (emulationPreventionBytesPositions.length === 0) {
- return data;
- }
- // Create a new array to hold the NAL unit data
- newLength = length - emulationPreventionBytesPositions.length;
- newData = new Uint8Array(newLength);
- var sourceIndex = 0;
- for (i = 0; i < newLength; sourceIndex++, i++) {
- if (sourceIndex === emulationPreventionBytesPositions[0]) {
- // Skip this byte
- sourceIndex++;
- // Remove this position index
- emulationPreventionBytesPositions.shift();
- }
- newData[i] = data[sourceIndex];
- }
- return newData;
- };
- /**
- * Read a sequence parameter set and return some interesting video
- * properties. A sequence parameter set is the H264 metadata that
- * describes the properties of upcoming video frames.
- * @param data {Uint8Array} the bytes of a sequence parameter set
- * @return {object} an object with configuration parsed from the
- * sequence parameter set, including the dimensions of the
- * associated video frames.
- */
- readSequenceParameterSet = function(data) {
- var
- frameCropLeftOffset = 0,
- frameCropRightOffset = 0,
- frameCropTopOffset = 0,
- frameCropBottomOffset = 0,
- sarScale = 1,
- expGolombDecoder, profileIdc, levelIdc, profileCompatibility,
- chromaFormatIdc, picOrderCntType,
- numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1,
- picHeightInMapUnitsMinus1,
- frameMbsOnlyFlag,
- scalingListCount,
- sarRatio,
- aspectRatioIdc,
- i;
- expGolombDecoder = new ExpGolomb(data);
- profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
- profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
- levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
- expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
- // some profiles have more optional data we don't need
- if (profileIdc === 100 ||
- profileIdc === 110 ||
- profileIdc === 122 ||
- profileIdc === 244 ||
- profileIdc === 44 ||
- profileIdc === 83 ||
- profileIdc === 86 ||
- profileIdc === 118 ||
- profileIdc === 128 ||
- profileIdc === 138 ||
- profileIdc === 139 ||
- profileIdc === 134) {
- chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
- if (chromaFormatIdc === 3) {
- expGolombDecoder.skipBits(1); // separate_colour_plane_flag
- }
- expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
- expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
- expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
- if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag
- scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
- for (i = 0; i < scalingListCount; i++) {
- if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ]
- if (i < 6) {
- skipScalingList(16, expGolombDecoder);
- } else {
- skipScalingList(64, expGolombDecoder);
- }
- }
- }
- }
- }
- expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
- picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
- if (picOrderCntType === 0) {
- expGolombDecoder.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
- } else if (picOrderCntType === 1) {
- expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
- expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
- expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
- numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
- for(i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
- expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
- }
- }
- expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
- expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
- picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
- picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
- frameMbsOnlyFlag = expGolombDecoder.readBits(1);
- if (frameMbsOnlyFlag === 0) {
- expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
- }
- expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
- if (expGolombDecoder.readBoolean()) { // frame_cropping_flag
- frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
- frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
- frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
- frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
- }
- if (expGolombDecoder.readBoolean()) {
- // vui_parameters_present_flag
- if (expGolombDecoder.readBoolean()) {
- // aspect_ratio_info_present_flag
- aspectRatioIdc = expGolombDecoder.readUnsignedByte();
- switch (aspectRatioIdc) {
- case 1: sarRatio = [1,1]; break;
- case 2: sarRatio = [12,11]; break;
- case 3: sarRatio = [10,11]; break;
- case 4: sarRatio = [16,11]; break;
- case 5: sarRatio = [40,33]; break;
- case 6: sarRatio = [24,11]; break;
- case 7: sarRatio = [20,11]; break;
- case 8: sarRatio = [32,11]; break;
- case 9: sarRatio = [80,33]; break;
- case 10: sarRatio = [18,11]; break;
- case 11: sarRatio = [15,11]; break;
- case 12: sarRatio = [64,33]; break;
- case 13: sarRatio = [160,99]; break;
- case 14: sarRatio = [4,3]; break;
- case 15: sarRatio = [3,2]; break;
- case 16: sarRatio = [2,1]; break;
- case 255: {
- sarRatio = [expGolombDecoder.readUnsignedByte() << 8 |
- expGolombDecoder.readUnsignedByte(),
- expGolombDecoder.readUnsignedByte() << 8 |
- expGolombDecoder.readUnsignedByte() ];
- break;
- }
- }
- if (sarRatio) {
- sarScale = sarRatio[0] / sarRatio[1];
- }
- }
- }
- return {
- profileIdc: profileIdc,
- levelIdc: levelIdc,
- profileCompatibility: profileCompatibility,
- width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
- height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2)
- };
- };
- };
- H264Stream.prototype = new Stream();
- module.exports = {
- H264Stream: H264Stream,
- NalByteStream: NalByteStream,
- };
- },{"../utils/exp-golomb.js":54,"../utils/stream.js":55}],39:[function(require,module,exports){
- module.exports = {
- adts: require('./adts'),
- h264: require('./h264'),
- };
- },{"./adts":37,"./h264":38}],40:[function(require,module,exports){
- /**
- * An object that stores the bytes of an FLV tag and methods for
- * querying and manipulating that data.
- * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
- */
- 'use strict';
- var FlvTag;
- // (type:uint, extraData:Boolean = false) extends ByteArray
- FlvTag = function(type, extraData) {
- var
- // Counter if this is a metadata tag, nal start marker if this is a video
- // tag. unused if this is an audio tag
- adHoc = 0, // :uint
- // The default size is 16kb but this is not enough to hold iframe
- // data and the resizing algorithm costs a bit so we create a larger
- // starting buffer for video tags
- bufferStartSize = 16384,
- // checks whether the FLV tag has enough capacity to accept the proposed
- // write and re-allocates the internal buffers if necessary
- prepareWrite = function(flv, count) {
- var
- bytes,
- minLength = flv.position + count;
- if (minLength < flv.bytes.byteLength) {
- // there's enough capacity so do nothing
- return;
- }
- // allocate a new buffer and copy over the data that will not be modified
- bytes = new Uint8Array(minLength * 2);
- bytes.set(flv.bytes.subarray(0, flv.position), 0);
- flv.bytes = bytes;
- flv.view = new DataView(flv.bytes.buffer);
- },
- // commonly used metadata properties
- widthBytes = FlvTag.widthBytes || new Uint8Array('width'.length),
- heightBytes = FlvTag.heightBytes || new Uint8Array('height'.length),
- videocodecidBytes = FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length),
- i;
- if (!FlvTag.widthBytes) {
- // calculating the bytes of common metadata names ahead of time makes the
- // corresponding writes faster because we don't have to loop over the
- // characters
- // re-test with test/perf.html if you're planning on changing this
- for (i = 0; i < 'width'.length; i++) {
- widthBytes[i] = 'width'.charCodeAt(i);
- }
- for (i = 0; i < 'height'.length; i++) {
- heightBytes[i] = 'height'.charCodeAt(i);
- }
- for (i = 0; i < 'videocodecid'.length; i++) {
- videocodecidBytes[i] = 'videocodecid'.charCodeAt(i);
- }
- FlvTag.widthBytes = widthBytes;
- FlvTag.heightBytes = heightBytes;
- FlvTag.videocodecidBytes = videocodecidBytes;
- }
- this.keyFrame = false; // :Boolean
- switch(type) {
- case FlvTag.VIDEO_TAG:
- this.length = 16;
- // Start the buffer at 256k
- bufferStartSize *= 6;
- break;
- case FlvTag.AUDIO_TAG:
- this.length = 13;
- this.keyFrame = true;
- break;
- case FlvTag.METADATA_TAG:
- this.length = 29;
- this.keyFrame = true;
- break;
- default:
- throw("Error Unknown TagType");
- }
- this.bytes = new Uint8Array(bufferStartSize);
- this.view = new DataView(this.bytes.buffer);
- this.bytes[0] = type;
- this.position = this.length;
- this.keyFrame = extraData; // Defaults to false
- // presentation timestamp
- this.pts = 0;
- // decoder timestamp
- this.dts = 0;
- // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0)
- this.writeBytes = function(bytes, offset, length) {
- var
- start = offset || 0,
- end;
- length = length || bytes.byteLength;
- end = start + length;
- prepareWrite(this, length);
- this.bytes.set(bytes.subarray(start, end), this.position);
- this.position += length;
- this.length = Math.max(this.length, this.position);
- };
- // ByteArray#writeByte(value:int):void
- this.writeByte = function(byte) {
- prepareWrite(this, 1);
- this.bytes[this.position] = byte;
- this.position++;
- this.length = Math.max(this.length, this.position);
- };
- // ByteArray#writeShort(value:int):void
- this.writeShort = function(short) {
- prepareWrite(this, 2);
- this.view.setUint16(this.position, short);
- this.position += 2;
- this.length = Math.max(this.length, this.position);
- };
- // Negative index into array
- // (pos:uint):int
- this.negIndex = function(pos) {
- return this.bytes[this.length - pos];
- };
- // The functions below ONLY work when this[0] == VIDEO_TAG.
- // We are not going to check for that because we dont want the overhead
- // (nal:ByteArray = null):int
- this.nalUnitSize = function() {
- if (adHoc === 0) {
- return 0;
- }
- return this.length - (adHoc + 4);
- };
- this.startNalUnit = function() {
- // remember position and add 4 bytes
- if (adHoc > 0) {
- throw new Error("Attempted to create new NAL wihout closing the old one");
- }
- // reserve 4 bytes for nal unit size
- adHoc = this.length;
- this.length += 4;
- this.position = this.length;
- };
- // (nal:ByteArray = null):void
- this.endNalUnit = function(nalContainer) {
- var
- nalStart, // :uint
- nalLength; // :uint
- // Rewind to the marker and write the size
- if (this.length === adHoc + 4) {
- // we started a nal unit, but didnt write one, so roll back the 4 byte size value
- this.length -= 4;
- } else if (adHoc > 0) {
- nalStart = adHoc + 4;
- nalLength = this.length - nalStart;
- this.position = adHoc;
- this.view.setUint32(this.position, nalLength);
- this.position = this.length;
- if (nalContainer) {
- // Add the tag to the NAL unit
- nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength));
- }
- }
- adHoc = 0;
- };
- /**
- * Write out a 64-bit floating point valued metadata property. This method is
- * called frequently during a typical parse and needs to be fast.
- */
- // (key:String, val:Number):void
- this.writeMetaDataDouble = function(key, val) {
- var i;
- prepareWrite(this, 2 + key.length + 9);
- // write size of property name
- this.view.setUint16(this.position, key.length);
- this.position += 2;
- // this next part looks terrible but it improves parser throughput by
- // 10kB/s in my testing
- // write property name
- if (key === 'width') {
- this.bytes.set(widthBytes, this.position);
- this.position += 5;
- } else if (key === 'height') {
- this.bytes.set(heightBytes, this.position);
- this.position += 6;
- } else if (key === 'videocodecid') {
- this.bytes.set(videocodecidBytes, this.position);
- this.position += 12;
- } else {
- for (i = 0; i < key.length; i++) {
- this.bytes[this.position] = key.charCodeAt(i);
- this.position++;
- }
- }
- // skip null byte
- this.position++;
- // write property value
- this.view.setFloat64(this.position, val);
- this.position += 8;
- // update flv tag length
- this.length = Math.max(this.length, this.position);
- ++adHoc;
- };
- // (key:String, val:Boolean):void
- this.writeMetaDataBoolean = function(key, val) {
- var i;
- prepareWrite(this, 2);
- this.view.setUint16(this.position, key.length);
- this.position += 2;
- for (i = 0; i < key.length; i++) {
- // if key.charCodeAt(i) >= 255, handle error
- prepareWrite(this, 1);
- this.bytes[this.position] = key.charCodeAt(i);
- this.position++;
- }
- prepareWrite(this, 2);
- this.view.setUint8(this.position, 0x01);
- this.position++;
- this.view.setUint8(this.position, val ? 0x01 : 0x00);
- this.position++;
- this.length = Math.max(this.length, this.position);
- ++adHoc;
- };
- // ():ByteArray
- this.finalize = function() {
- var
- dtsDelta, // :int
- len; // :int
- switch(this.bytes[0]) {
- // Video Data
- case FlvTag.VIDEO_TAG:
- this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame)
- this.bytes[12] = extraData ? 0x00 : 0x01;
- dtsDelta = this.pts - this.dts;
- this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16;
- this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8;
- this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0;
- break;
- case FlvTag.AUDIO_TAG:
- this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo
- this.bytes[12] = extraData ? 0x00 : 0x01;
- break;
- case FlvTag.METADATA_TAG:
- this.position = 11;
- this.view.setUint8(this.position, 0x02); // String type
- this.position++;
- this.view.setUint16(this.position, 0x0A); // 10 Bytes
- this.position += 2;
- // set "onMetaData"
- this.bytes.set([0x6f, 0x6e, 0x4d, 0x65,
- 0x74, 0x61, 0x44, 0x61,
- 0x74, 0x61], this.position);
- this.position += 10;
- this.bytes[this.position] = 0x08; // Array type
- this.position++;
- this.view.setUint32(this.position, adHoc);
- this.position = this.length;
- this.bytes.set([0, 0, 9], this.position);
- this.position += 3; // End Data Tag
- this.length = this.position;
- break;
- }
- len = this.length - 11;
- // write the DataSize field
- this.bytes[ 1] = (len & 0x00FF0000) >>> 16;
- this.bytes[ 2] = (len & 0x0000FF00) >>> 8;
- this.bytes[ 3] = (len & 0x000000FF) >>> 0;
- // write the Timestamp
- this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16;
- this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8;
- this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0;
- this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24;
- // write the StreamID
- this.bytes[ 8] = 0;
- this.bytes[ 9] = 0;
- this.bytes[10] = 0;
- // Sometimes we're at the end of the view and have one slot to write a
- // uint32, so, prepareWrite of count 4, since, view is uint8
- prepareWrite(this, 4);
- this.view.setUint32(this.length, this.length);
- this.length += 4;
- this.position += 4;
- // trim down the byte buffer to what is actually being used
- this.bytes = this.bytes.subarray(0, this.length);
- this.frameTime = FlvTag.frameTime(this.bytes);
- // if bytes.bytelength isn't equal to this.length, handle error
- return this;
- };
- };
- FlvTag.AUDIO_TAG = 0x08; // == 8, :uint
- FlvTag.VIDEO_TAG = 0x09; // == 9, :uint
- FlvTag.METADATA_TAG = 0x12; // == 18, :uint
- // (tag:ByteArray):Boolean {
- FlvTag.isAudioFrame = function(tag) {
- return FlvTag.AUDIO_TAG === tag[0];
- };
- // (tag:ByteArray):Boolean {
- FlvTag.isVideoFrame = function(tag) {
- return FlvTag.VIDEO_TAG === tag[0];
- };
- // (tag:ByteArray):Boolean {
- FlvTag.isMetaData = function(tag) {
- return FlvTag.METADATA_TAG === tag[0];
- };
- // (tag:ByteArray):Boolean {
- FlvTag.isKeyFrame = function(tag) {
- if (FlvTag.isVideoFrame(tag)) {
- return tag[11] === 0x17;
- }
- if (FlvTag.isAudioFrame(tag)) {
- return true;
- }
- if (FlvTag.isMetaData(tag)) {
- return true;
- }
- return false;
- };
- // (tag:ByteArray):uint {
- FlvTag.frameTime = function(tag) {
- var pts = tag[ 4] << 16; // :uint
- pts |= tag[ 5] << 8;
- pts |= tag[ 6] << 0;
- pts |= tag[ 7] << 24;
- return pts;
- };
- module.exports = FlvTag;
- },{}],41:[function(require,module,exports){
- module.exports = {
- tag: require('./flv-tag'),
- Transmuxer: require('./transmuxer'),
- tools: require('../tools/flv-inspector'),
- };
- },{"../tools/flv-inspector":52,"./flv-tag":40,"./transmuxer":42}],42:[function(require,module,exports){
- 'use strict';
- var Stream = require('../utils/stream.js');
- var FlvTag = require('./flv-tag.js');
- var m2ts = require('../m2ts/m2ts.js');
- var AdtsStream = require('../codecs/adts.js');
- var H264Stream = require('../codecs/h264').H264Stream;
- var
- MetadataStream,
- Transmuxer,
- VideoSegmentStream,
- AudioSegmentStream,
- CoalesceStream,
- collectTimelineInfo,
- metaDataTag,
- extraDataTag;
- /**
- * Store information about the start and end of the tracka and the
- * duration for each frame/sample we process in order to calculate
- * the baseMediaDecodeTime
- */
- collectTimelineInfo = function (track, data) {
- if (typeof data.pts === 'number') {
- if (track.timelineStartInfo.pts === undefined) {
- track.timelineStartInfo.pts = data.pts;
- } else {
- track.timelineStartInfo.pts =
- Math.min(track.timelineStartInfo.pts, data.pts);
- }
- }
- if (typeof data.dts === 'number') {
- if (track.timelineStartInfo.dts === undefined) {
- track.timelineStartInfo.dts = data.dts;
- } else {
- track.timelineStartInfo.dts =
- Math.min(track.timelineStartInfo.dts, data.dts);
- }
- }
- };
- metaDataTag = function(track, pts) {
- var
- tag = new FlvTag(FlvTag.METADATA_TAG); // :FlvTag
- tag.dts = pts;
- tag.pts = pts;
- tag.writeMetaDataDouble("videocodecid", 7);
- tag.writeMetaDataDouble("width", track.width);
- tag.writeMetaDataDouble("height", track.height);
- return tag;
- };
- extraDataTag = function(track, pts) {
- var
- i,
- tag = new FlvTag(FlvTag.VIDEO_TAG, true);
- tag.dts = pts;
- tag.pts = pts;
- tag.writeByte(0x01);// version
- tag.writeByte(track.profileIdc);// profile
- tag.writeByte(track.profileCompatibility);// compatibility
- tag.writeByte(track.levelIdc);// level
- tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
- tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
- tag.writeShort( track.sps[0].length ); // data of SPS
- tag.writeBytes( track.sps[0] ); // SPS
- tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?)
- for (i = 0 ; i < track.pps.length ; ++i) {
- tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS
- tag.writeBytes(track.pps[i]); // data of PPS
- }
- return tag;
- };
- /**
- * Constructs a single-track, media segment from AAC data
- * events. The output of this stream can be fed to flash.
- */
- AudioSegmentStream = function(track) {
- var
- adtsFrames = [],
- adtsFramesLength = 0,
- sequenceNumber = 0,
- earliestAllowedDts = 0,
- oldExtraData;
- AudioSegmentStream.prototype.init.call(this);
- this.push = function(data) {
- collectTimelineInfo(track, data);
- if (track && track.channelcount === undefined) {
- track.audioobjecttype = data.audioobjecttype;
- track.channelcount = data.channelcount;
- track.samplerate = data.samplerate;
- track.samplingfrequencyindex = data.samplingfrequencyindex;
- track.samplesize = data.samplesize;
- track.extraData = (track.audioobjecttype << 11) |
- (track.samplingfrequencyindex << 7) |
- (track.channelcount << 3);
- }
- data.pts = Math.round(data.pts / 90);
- data.dts = Math.round(data.dts / 90);
- // buffer audio data until end() is called
- adtsFrames.push(data);
- };
- this.flush = function() {
- var currentFrame, adtsFrame, deltaDts,lastMetaPts, tags = [];
- // return early if no audio data has been observed
- if (adtsFrames.length === 0) {
- this.trigger('done');
- return;
- }
- lastMetaPts = -Infinity;
- while (adtsFrames.length) {
- currentFrame = adtsFrames.shift();
- // write out metadata tags every 1 second so that the decoder
- // is re-initialized quickly after seeking into a different
- // audio configuration
- if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) {
- adtsFrame = new FlvTag(FlvTag.METADATA_TAG);
- adtsFrame.pts = currentFrame.pts;
- adtsFrame.dts = currentFrame.dts;
- // AAC is always 10
- adtsFrame.writeMetaDataDouble("audiocodecid", 10);
- adtsFrame.writeMetaDataBoolean("stereo", 2 === track.channelcount);
- adtsFrame.writeMetaDataDouble ("audiosamplerate", track.samplerate);
- // Is AAC always 16 bit?
- adtsFrame.writeMetaDataDouble ("audiosamplesize", 16);
- tags.push(adtsFrame);
- oldExtraData = track.extraData;
- adtsFrame = new FlvTag(FlvTag.AUDIO_TAG, true);
- // For audio, DTS is always the same as PTS. We want to set the DTS
- // however so we can compare with video DTS to determine approximate
- // packet order
- adtsFrame.pts = currentFrame.pts;
- adtsFrame.dts = currentFrame.dts;
- adtsFrame.view.setUint16(adtsFrame.position, track.extraData);
- adtsFrame.position += 2;
- adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position);
- tags.push(adtsFrame);
- lastMetaPts = currentFrame.pts;
- }
- adtsFrame = new FlvTag(FlvTag.AUDIO_TAG);
- adtsFrame.pts = currentFrame.pts;
- adtsFrame.dts = currentFrame.dts;
- adtsFrame.writeBytes(currentFrame.data);
- tags.push(adtsFrame);
- }
- oldExtraData = null;
- this.trigger('data', {track: track, tags: tags});
- this.trigger('done');
- };
- };
- AudioSegmentStream.prototype = new Stream();
- /**
- * Store FlvTags for the h264 stream
- * @param track {object} track metadata configuration
- */
- VideoSegmentStream = function(track) {
- var
- sequenceNumber = 0,
- nalUnits = [],
- nalUnitsLength = 0,
- config,
- h264Frame;
- VideoSegmentStream.prototype.init.call(this);
- this.finishFrame = function(tags, frame) {
- if (!frame) {
- return;
- }
- // Check if keyframe and the length of tags.
- // This makes sure we write metadata on the first frame of a segment.
- if (config && track && track.newMetadata &&
- (frame.keyFrame || tags.length === 0)) {
- // Push extra data on every IDR frame in case we did a stream change + seek
- tags.push(metaDataTag(config, frame.pts));
- tags.push(extraDataTag(track, frame.pts));
- track.newMetadata = false;
- }
- frame.endNalUnit();
- tags.push(frame);
- };
- this.push = function(data) {
- collectTimelineInfo(track, data);
- data.pts = Math.round(data.pts / 90);
- data.dts = Math.round(data.dts / 90);
- // buffer video until flush() is called
- nalUnits.push(data);
- };
- this.flush = function() {
- var
- currentNal,
- tags = [];
- // Throw away nalUnits at the start of the byte stream until we find
- // the first AUD
- while (nalUnits.length) {
- if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
- break;
- }
- nalUnits.shift();
- }
- // return early if no video data has been observed
- if (nalUnits.length === 0) {
- this.trigger('done');
- return;
- }
- while (nalUnits.length) {
- currentNal = nalUnits.shift();
- // record the track config
- if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') {
- track.newMetadata = true;
- config = currentNal.config;
- track.width = config.width;
- track.height = config.height;
- track.sps = [currentNal.data];
- track.profileIdc = config.profileIdc;
- track.levelIdc = config.levelIdc;
- track.profileCompatibility = config.profileCompatibility;
- h264Frame.endNalUnit();
- } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') {
- track.newMetadata = true;
- track.pps = [currentNal.data];
- h264Frame.endNalUnit();
- } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
- if (h264Frame) {
- this.finishFrame(tags, h264Frame);
- }
- h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
- h264Frame.pts = currentNal.pts;
- h264Frame.dts = currentNal.dts;
- } else {
- if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
- // the current sample is a key frame
- h264Frame.keyFrame = true;
- }
- h264Frame.endNalUnit();
- }
- h264Frame.startNalUnit();
- h264Frame.writeBytes(currentNal.data);
- }
- if (h264Frame) {
- this.finishFrame(tags, h264Frame);
- }
- this.trigger('data', {track: track, tags: tags});
- // Continue with the flush process now
- this.trigger('done');
- };
- };
- VideoSegmentStream.prototype = new Stream();
- /**
- * The final stage of the transmuxer that emits the flv tags
- * for audio, video, and metadata. Also tranlates in time and
- * outputs caption data and id3 cues.
- */
- CoalesceStream = function(options) {
- // Number of Tracks per output segment
- // If greater than 1, we combine multiple
- // tracks into a single segment
- this.numberOfTracks = 0;
- this.metadataStream = options.metadataStream;
- this.videoTags = [];
- this.audioTags = [];
- this.videoTrack = null;
- this.audioTrack = null;
- this.pendingCaptions = [];
- this.pendingMetadata = [];
- this.pendingTracks = 0;
- CoalesceStream.prototype.init.call(this);
- // Take output from multiple
- this.push = function(output) {
- // buffer incoming captions until the associated video segment
- // finishes
- if (output.text) {
- return this.pendingCaptions.push(output);
- }
- // buffer incoming id3 tags until the final flush
- if (output.frames) {
- return this.pendingMetadata.push(output);
- }
- if (output.track.type === 'video') {
- this.videoTrack = output.track;
- this.videoTags = output.tags;
- this.pendingTracks++;
- }
- if (output.track.type === 'audio') {
- this.audioTrack = output.track;
- this.audioTags = output.tags;
- this.pendingTracks++;
- }
- };
- };
- CoalesceStream.prototype = new Stream();
- CoalesceStream.prototype.flush = function() {
- var
- id3,
- caption,
- i,
- timelineStartPts,
- event = {
- tags: {},
- captions: [],
- metadata: []
- };
- if (this.pendingTracks < this.numberOfTracks) {
- return;
- }
- if (this.videoTrack) {
- timelineStartPts = this.videoTrack.timelineStartInfo.pts;
- } else if (this.audioTrack) {
- timelineStartPts = this.audioTrack.timelineStartInfo.pts;
- }
- event.tags.videoTags = this.videoTags;
- event.tags.audioTags = this.audioTags;
- // Translate caption PTS times into second offsets into the
- // video timeline for the segment
- for (i = 0; i < this.pendingCaptions.length; i++) {
- caption = this.pendingCaptions[i];
- caption.startTime = caption.startPts - timelineStartPts;
- caption.startTime /= 90e3;
- caption.endTime = caption.endPts - timelineStartPts;
- caption.endTime /= 90e3;
- event.captions.push(caption);
- }
- // Translate ID3 frame PTS times into second offsets into the
- // video timeline for the segment
- for (i = 0; i < this.pendingMetadata.length; i++) {
- id3 = this.pendingMetadata[i];
- id3.cueTime = id3.pts - timelineStartPts;
- id3.cueTime /= 90e3;
- event.metadata.push(id3);
- }
- // We add this to every single emitted segment even though we only need
- // it for the first
- event.metadata.dispatchType = this.metadataStream.dispatchType;
- // Reset stream state
- this.videoTrack = null;
- this.audioTrack = null;
- this.videoTags = [];
- this.audioTags = [];
- this.pendingCaptions.length = 0;
- this.pendingMetadata.length = 0;
- this.pendingTracks = 0;
- // Emit the final segment
- this.trigger('data', event);
- this.trigger('done');
- };
- /**
- * An object that incrementally transmuxes MPEG2 Trasport Stream
- * chunks into an FLV.
- */
- Transmuxer = function(options) {
- var
- self = this,
- videoTrack,
- audioTrack,
- packetStream, parseStream, elementaryStream,
- adtsStream, h264Stream,
- videoSegmentStream, audioSegmentStream, captionStream,
- coalesceStream;
- Transmuxer.prototype.init.call(this);
- options = options || {};
- // expose the metadata stream
- this.metadataStream = new m2ts.MetadataStream();
- options.metadataStream = this.metadataStream;
- // set up the parsing pipeline
- packetStream = new m2ts.TransportPacketStream();
- parseStream = new m2ts.TransportParseStream();
- elementaryStream = new m2ts.ElementaryStream();
- adtsStream = new AdtsStream();
- h264Stream = new H264Stream();
- coalesceStream = new CoalesceStream(options);
- // disassemble MPEG2-TS packets into elementary streams
- packetStream
- .pipe(parseStream)
- .pipe(elementaryStream);
- // !!THIS ORDER IS IMPORTANT!!
- // demux the streams
- elementaryStream
- .pipe(h264Stream);
- elementaryStream
- .pipe(adtsStream);
- elementaryStream
- .pipe(this.metadataStream)
- .pipe(coalesceStream);
- // if CEA-708 parsing is available, hook up a caption stream
- captionStream = new m2ts.CaptionStream();
- h264Stream.pipe(captionStream)
- .pipe(coalesceStream);
- // hook up the segment streams once track metadata is delivered
- elementaryStream.on('data', function(data) {
- var i, videoTrack, audioTrack;
- if (data.type === 'metadata') {
- i = data.tracks.length;
- // scan the tracks listed in the metadata
- while (i--) {
- if (data.tracks[i].type === 'video') {
- videoTrack = data.tracks[i];
- } else if (data.tracks[i].type === 'audio') {
- audioTrack = data.tracks[i];
- }
- }
- // hook up the video segment stream to the first track with h264 data
- if (videoTrack && !videoSegmentStream) {
- coalesceStream.numberOfTracks++;
- videoSegmentStream = new VideoSegmentStream(videoTrack);
- // Set up the final part of the video pipeline
- h264Stream
- .pipe(videoSegmentStream)
- .pipe(coalesceStream);
- }
- if (audioTrack && !audioSegmentStream) {
- // hook up the audio segment stream to the first track with aac data
- coalesceStream.numberOfTracks++;
- audioSegmentStream = new AudioSegmentStream(audioTrack);
- // Set up the final part of the audio pipeline
- adtsStream
- .pipe(audioSegmentStream)
- .pipe(coalesceStream);
- }
- }
- });
- // feed incoming data to the front of the parsing pipeline
- this.push = function(data) {
- packetStream.push(data);
- };
- // flush any buffered data
- this.flush = function() {
- // Start at the top of the pipeline and flush all pending work
- packetStream.flush();
- };
- // Re-emit any data coming from the coalesce stream to the outside world
- coalesceStream.on('data', function (event) {
- self.trigger('data', event);
- });
- // Let the consumer know we have finished flushing the entire pipeline
- coalesceStream.on('done', function () {
- self.trigger('done');
- });
- // For information on the FLV format, see
- // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf.
- // Technically, this function returns the header and a metadata FLV tag
- // if duration is greater than zero
- // duration in seconds
- // @return {object} the bytes of the FLV header as a Uint8Array
- this.getFlvHeader = function(duration, audio, video) { // :ByteArray {
- var
- headBytes = new Uint8Array(3 + 1 + 1 + 4),
- head = new DataView(headBytes.buffer),
- metadata,
- result,
- metadataLength;
- // default arguments
- duration = duration || 0;
- audio = audio === undefined? true : audio;
- video = video === undefined? true : video;
- // signature
- head.setUint8(0, 0x46); // 'F'
- head.setUint8(1, 0x4c); // 'L'
- head.setUint8(2, 0x56); // 'V'
- // version
- head.setUint8(3, 0x01);
- // flags
- head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00));
- // data offset, should be 9 for FLV v1
- head.setUint32(5, headBytes.byteLength);
- // init the first FLV tag
- if (duration <= 0) {
- // no duration available so just write the first field of the first
- // FLV tag
- result = new Uint8Array(headBytes.byteLength + 4);
- result.set(headBytes);
- result.set([0, 0, 0, 0], headBytes.byteLength);
- return result;
- }
- // write out the duration metadata tag
- metadata = new FlvTag(FlvTag.METADATA_TAG);
- metadata.pts = metadata.dts = 0;
- metadata.writeMetaDataDouble("duration", duration);
- metadataLength = metadata.finalize().length;
- result = new Uint8Array(headBytes.byteLength + metadataLength);
- result.set(headBytes);
- result.set(head.byteLength, metadataLength);
- return result;
- };
- };
- Transmuxer.prototype = new Stream();
- // forward compatibility
- module.exports = Transmuxer;
- },{"../codecs/adts.js":37,"../codecs/h264":38,"../m2ts/m2ts.js":46,"../utils/stream.js":55,"./flv-tag.js":40}],43:[function(require,module,exports){
- 'use strict';
- var muxjs = {
- codecs: require('./codecs'),
- mp4: require('./mp4'),
- flv: require('./flv'),
- mp2t: require('./m2ts'),
- };
- module.exports = muxjs;
- },{"./codecs":39,"./flv":41,"./m2ts":45,"./mp4":49}],44:[function(require,module,exports){
- /**
- * mux.js
- *
- * Copyright (c) 2015 Brightcove
- * All rights reserved.
- *
- * Reads in-band caption information from a video elementary
- * stream. Captions must follow the CEA-708 standard for injection
- * into an MPEG-2 transport streams.
- * @see https://en.wikipedia.org/wiki/CEA-708
- */
- 'use strict';
- // -----------------
- // Link To Transport
- // -----------------
- // Supplemental enhancement information (SEI) NAL units have a
- // payload type field to indicate how they are to be
- // interpreted. CEAS-708 caption content is always transmitted with
- // payload type 0x04.
- var USER_DATA_REGISTERED_ITU_T_T35 = 4,
- RBSP_TRAILING_BITS = 128,
- Stream = require('../utils/stream');
- /**
- * Parse a supplemental enhancement information (SEI) NAL unit.
- * Stops parsing once a message of type ITU T T35 has been found.
- *
- * @param bytes {Uint8Array} the bytes of a SEI NAL unit
- * @return {object} the parsed SEI payload
- * @see Rec. ITU-T H.264, 7.3.2.3.1
- */
- var parseSei = function(bytes) {
- var
- i = 0,
- result = {
- payloadType: -1,
- payloadSize: 0,
- },
- payloadType = 0,
- payloadSize = 0;
- // go through the sei_rbsp parsing each each individual sei_message
- while (i < bytes.byteLength) {
- // stop once we have hit the end of the sei_rbsp
- if (bytes[i] === RBSP_TRAILING_BITS) {
- break;
- }
- // Parse payload type
- while (bytes[i] === 0xFF) {
- payloadType += 255;
- i++;
- }
- payloadType += bytes[i++];
- // Parse payload size
- while (bytes[i] === 0xFF) {
- payloadSize += 255;
- i++;
- }
- payloadSize += bytes[i++];
- // this sei_message is a 608/708 caption so save it and break
- // there can only ever be one caption message in a frame's sei
- if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
- result.payloadType = payloadType;
- result.payloadSize = payloadSize;
- result.payload = bytes.subarray(i, i + payloadSize);
- break;
- }
- // skip the payload and parse the next message
- i += payloadSize;
- payloadType = 0;
- payloadSize = 0;
- }
- return result;
- };
- // see ANSI/SCTE 128-1 (2013), section 8.1
- var parseUserData = function(sei) {
- // itu_t_t35_contry_code must be 181 (United States) for
- // captions
- if (sei.payload[0] !== 181) {
- return null;
- }
- // itu_t_t35_provider_code should be 49 (ATSC) for captions
- if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) {
- return null;
- }
- // the user_identifier should be "GA94" to indicate ATSC1 data
- if (String.fromCharCode(sei.payload[3],
- sei.payload[4],
- sei.payload[5],
- sei.payload[6]) !== 'GA94') {
- return null;
- }
- // finally, user_data_type_code should be 0x03 for caption data
- if (sei.payload[7] !== 0x03) {
- return null;
- }
- // return the user_data_type_structure and strip the trailing
- // marker bits
- return sei.payload.subarray(8, sei.payload.length - 1);
- };
- // see CEA-708-D, section 4.4
- var parseCaptionPackets = function(pts, userData) {
- var results = [], i, count, offset, data;
- // if this is just filler, return immediately
- if (!(userData[0] & 0x40)) {
- return results;
- }
- // parse out the cc_data_1 and cc_data_2 fields
- count = userData[0] & 0x1f;
- for (i = 0; i < count; i++) {
- offset = i * 3;
- data = {
- type: userData[offset + 2] & 0x03,
- pts: pts
- };
- // capture cc data when cc_valid is 1
- if (userData[offset + 2] & 0x04) {
- data.ccData = (userData[offset + 3] << 8) | userData[offset + 4];
- results.push(data);
- }
- }
- return results;
- };
- var CaptionStream = function() {
- var self = this;
- CaptionStream.prototype.init.call(this);
- this.captionPackets_ = [];
- this.field1_ = new Cea608Stream();
- // forward data and done events from field1_ to this CaptionStream
- this.field1_.on('data', this.trigger.bind(this, 'data'));
- this.field1_.on('done', this.trigger.bind(this, 'done'));
- };
- CaptionStream.prototype = new Stream();
- CaptionStream.prototype.push = function(event) {
- var sei, userData, captionPackets;
- // only examine SEI NALs
- if (event.nalUnitType !== 'sei_rbsp') {
- return;
- }
- // parse the sei
- sei = parseSei(event.escapedRBSP);
- // ignore everything but user_data_registered_itu_t_t35
- if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) {
- return;
- }
- // parse out the user data payload
- userData = parseUserData(sei);
- // ignore unrecognized userData
- if (!userData) {
- return;
- }
- // parse out CC data packets and save them for later
- this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData));
- };
- CaptionStream.prototype.flush = function () {
- // make sure we actually parsed captions before proceeding
- if (!this.captionPackets_.length) {
- this.field1_.flush();
- return;
- }
- // sort caption byte-pairs based on their PTS values
- this.captionPackets_.sort(function(a, b) {
- return a.pts - b.pts;
- });
- // Push each caption into Cea608Stream
- this.captionPackets_.forEach(this.field1_.push, this.field1_);
- this.captionPackets_.length = 0;
- this.field1_.flush();
- return;
- };
- // ----------------------
- // Session to Application
- // ----------------------
- var BASIC_CHARACTER_TRANSLATION = {
- 0x2a: 0xe1,
- 0x5c: 0xe9,
- 0x5e: 0xed,
- 0x5f: 0xf3,
- 0x60: 0xfa,
- 0x7b: 0xe7,
- 0x7c: 0xf7,
- 0x7d: 0xd1,
- 0x7e: 0xf1,
- 0x7f: 0x2588
- };
- var getCharFromCode = function(code) {
- if(code === null) {
- return '';
- }
- code = BASIC_CHARACTER_TRANSLATION[code] || code;
- return String.fromCharCode(code);
- };
- // Constants for the byte codes recognized by Cea608Stream. This
- // list is not exhaustive. For a more comprehensive listing and
- // semantics see
- // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
- var PADDING = 0x0000,
- // Pop-on Mode
- RESUME_CAPTION_LOADING = 0x1420,
- END_OF_CAPTION = 0x142f,
- // Roll-up Mode
- ROLL_UP_2_ROWS = 0x1425,
- ROLL_UP_3_ROWS = 0x1426,
- ROLL_UP_4_ROWS = 0x1427,
- RESUME_DIRECT_CAPTIONING = 0x1429,
- CARRIAGE_RETURN = 0x142d,
- // Erasure
- BACKSPACE = 0x1421,
- ERASE_DISPLAYED_MEMORY = 0x142c,
- ERASE_NON_DISPLAYED_MEMORY = 0x142e;
- // the index of the last row in a CEA-608 display buffer
- var BOTTOM_ROW = 14;
- // CEA-608 captions are rendered onto a 34x15 matrix of character
- // cells. The "bottom" row is the last element in the outer array.
- var createDisplayBuffer = function() {
- var result = [], i = BOTTOM_ROW + 1;
- while (i--) {
- result.push('');
- }
- return result;
- };
- var Cea608Stream = function() {
- Cea608Stream.prototype.init.call(this);
- this.mode_ = 'popOn';
- // When in roll-up mode, the index of the last row that will
- // actually display captions. If a caption is shifted to a row
- // with a lower index than this, it is cleared from the display
- // buffer
- this.topRow_ = 0;
- this.startPts_ = 0;
- this.displayed_ = createDisplayBuffer();
- this.nonDisplayed_ = createDisplayBuffer();
- this.lastControlCode_ = null;
- this.push = function(packet) {
- // Ignore other channels
- if (packet.type !== 0) {
- return;
- }
- var data, swap, char0, char1;
- // remove the parity bits
- data = packet.ccData & 0x7f7f;
- // ignore duplicate control codes
- if (data === this.lastControlCode_) {
- this.lastControlCode_ = null;
- return;
- }
- // Store control codes
- if ((data & 0xf000) === 0x1000) {
- this.lastControlCode_ = data;
- } else {
- this.lastControlCode_ = null;
- }
- switch (data) {
- case PADDING:
- break;
- case RESUME_CAPTION_LOADING:
- this.mode_ = 'popOn';
- break;
- case END_OF_CAPTION:
- // if a caption was being displayed, it's gone now
- this.flushDisplayed(packet.pts);
- // flip memory
- swap = this.displayed_;
- this.displayed_ = this.nonDisplayed_;
- this.nonDisplayed_ = swap;
- // start measuring the time to display the caption
- this.startPts_ = packet.pts;
- break;
- case ROLL_UP_2_ROWS:
- this.topRow_ = BOTTOM_ROW - 1;
- this.mode_ = 'rollUp';
- break;
- case ROLL_UP_3_ROWS:
- this.topRow_ = BOTTOM_ROW - 2;
- this.mode_ = 'rollUp';
- break;
- case ROLL_UP_4_ROWS:
- this.topRow_ = BOTTOM_ROW - 3;
- this.mode_ = 'rollUp';
- break;
- case CARRIAGE_RETURN:
- this.flushDisplayed(packet.pts);
- this.shiftRowsUp_();
- this.startPts_ = packet.pts;
- break;
- case BACKSPACE:
- if (this.mode_ === 'popOn') {
- this.nonDisplayed_[BOTTOM_ROW] = this.nonDisplayed_[BOTTOM_ROW].slice(0, -1);
- } else {
- this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1);
- }
- break;
- case ERASE_DISPLAYED_MEMORY:
- this.flushDisplayed(packet.pts);
- this.displayed_ = createDisplayBuffer();
- break;
- case ERASE_NON_DISPLAYED_MEMORY:
- this.nonDisplayed_ = createDisplayBuffer();
- break;
- default:
- char0 = data >>> 8;
- char1 = data & 0xff;
- // Look for a Channel 1 Preamble Address Code
- if (char0 >= 0x10 && char0 <= 0x17 &&
- char1 >= 0x40 && char1 <= 0x7F &&
- (char0 !== 0x10 || char1 < 0x60)) {
- // Follow Safari's lead and replace the PAC with a space
- char0 = 0x20;
- // we only want one space so make the second character null
- // which will get become '' in getCharFromCode
- char1 = null;
- }
- // Look for special character sets
- if ((char0 === 0x11 || char0 === 0x19) &&
- (char1 >= 0x30 && char1 <= 0x3F)) {
- // Put in eigth note and space
- char0 = 0x266A;
- char1 = '';
- }
- // ignore unsupported control codes
- if ((char0 & 0xf0) === 0x10) {
- return;
- }
- // character handling is dependent on the current mode
- this[this.mode_](packet.pts, char0, char1);
- break;
- }
- };
- };
- Cea608Stream.prototype = new Stream();
- // Trigger a cue point that captures the current state of the
- // display buffer
- Cea608Stream.prototype.flushDisplayed = function(pts) {
- var content = this.displayed_
- // remove spaces from the start and end of the string
- .map(function(row) { return row.trim(); })
- // remove empty rows
- .filter(function(row) { return row.length; })
- // combine all text rows to display in one cue
- .join('\n');
- if (content.length) {
- this.trigger('data', {
- startPts: this.startPts_,
- endPts: pts,
- text: content
- });
- }
- };
- // Mode Implementations
- Cea608Stream.prototype.popOn = function(pts, char0, char1) {
- var baseRow = this.nonDisplayed_[BOTTOM_ROW];
- // buffer characters
- baseRow += getCharFromCode(char0);
- baseRow += getCharFromCode(char1);
- this.nonDisplayed_[BOTTOM_ROW] = baseRow;
- };
- Cea608Stream.prototype.rollUp = function(pts, char0, char1) {
- var baseRow = this.displayed_[BOTTOM_ROW];
- if (baseRow === '') {
- // we're starting to buffer new display input, so flush out the
- // current display
- this.flushDisplayed(pts);
- this.startPts_ = pts;
- }
- baseRow += getCharFromCode(char0);
- baseRow += getCharFromCode(char1);
- this.displayed_[BOTTOM_ROW] = baseRow;
- };
- Cea608Stream.prototype.shiftRowsUp_ = function() {
- var i;
- // clear out inactive rows
- for (i = 0; i < this.topRow_; i++) {
- this.displayed_[i] = '';
- }
- // shift displayed rows up
- for (i = this.topRow_; i < BOTTOM_ROW; i++) {
- this.displayed_[i] = this.displayed_[i + 1];
- }
- // clear out the bottom row
- this.displayed_[BOTTOM_ROW] = '';
- };
- // exports
- module.exports = {
- CaptionStream: CaptionStream,
- Cea608Stream: Cea608Stream,
- };
- },{"../utils/stream":55}],45:[function(require,module,exports){
- module.exports = require('./m2ts');
- },{"./m2ts":46}],46:[function(require,module,exports){
- /**
- * mux.js
- *
- * Copyright (c) 2015 Brightcove
- * All rights reserved.
- *
- * A stream-based mp2t to mp4 converter. This utility can be used to
- * deliver mp4s to a SourceBuffer on platforms that support native
- * Media Source Extensions.
- */
- 'use strict';
- var Stream = require('../utils/stream.js'),
- CaptionStream = require('./caption-stream'),
- StreamTypes = require('./stream-types');
- var Stream = require('../utils/stream.js');
- var m2tsStreamTypes = require('./stream-types.js');
- // object types
- var
- TransportPacketStream, TransportParseStream, ElementaryStream,
- AacStream, H264Stream, NalByteStream;
- // constants
- var
- MP2T_PACKET_LENGTH = 188, // bytes
- SYNC_BYTE = 0x47,
- /**
- * Splits an incoming stream of binary data into MPEG-2 Transport
- * Stream packets.
- */
- TransportPacketStream = function() {
- var
- buffer = new Uint8Array(MP2T_PACKET_LENGTH),
- bytesInBuffer = 0;
- TransportPacketStream.prototype.init.call(this);
- // Deliver new bytes to the stream.
- this.push = function(bytes) {
- var
- i = 0,
- startIndex = 0,
- endIndex = MP2T_PACKET_LENGTH,
- everything;
- // If there are bytes remaining from the last segment, prepend them to the
- // bytes that were pushed in
- if (bytesInBuffer) {
- everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
- everything.set(buffer.subarray(0, bytesInBuffer));
- everything.set(bytes, bytesInBuffer);
- bytesInBuffer = 0;
- } else {
- everything = bytes;
- }
- // While we have enough data for a packet
- while (endIndex < everything.byteLength) {
- // Look for a pair of start and end sync bytes in the data..
- if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
- // We found a packet so emit it and jump one whole packet forward in
- // the stream
- this.trigger('data', everything.subarray(startIndex, endIndex));
- startIndex += MP2T_PACKET_LENGTH;
- endIndex += MP2T_PACKET_LENGTH;
- continue;
- }
- // If we get here, we have somehow become de-synchronized and we need to step
- // forward one byte at a time until we find a pair of sync bytes that denote
- // a packet
- startIndex++;
- endIndex++;
- }
- // If there was some data left over at the end of the segment that couldn't
- // possibly be a whole packet, keep it because it might be the start of a packet
- // that continues in the next segment
- if (startIndex < everything.byteLength) {
- buffer.set(everything.subarray(startIndex), 0);
- bytesInBuffer = everything.byteLength - startIndex;
- }
- };
- this.flush = function () {
- // If the buffer contains a whole packet when we are being flushed, emit it
- // and empty the buffer. Otherwise hold onto the data because it may be
- // important for decoding the next segment
- if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
- this.trigger('data', buffer);
- bytesInBuffer = 0;
- }
- this.trigger('done');
- };
- };
- TransportPacketStream.prototype = new Stream();
- /**
- * Accepts an MP2T TransportPacketStream and emits data events with parsed
- * forms of the individual transport stream packets.
- */
- TransportParseStream = function() {
- var parsePsi, parsePat, parsePmt, parsePes, self;
- TransportParseStream.prototype.init.call(this);
- self = this;
- this.packetsWaitingForPmt = [];
- this.programMapTable = undefined;
- parsePsi = function(payload, psi) {
- var offset = 0;
- // PSI packets may be split into multiple sections and those
- // sections may be split into multiple packets. If a PSI
- // section starts in this packet, the payload_unit_start_indicator
- // will be true and the first byte of the payload will indicate
- // the offset from the current position to the start of the
- // section.
- if (psi.payloadUnitStartIndicator) {
- offset += payload[offset] + 1;
- }
- if (psi.type === 'pat') {
- parsePat(payload.subarray(offset), psi);
- } else {
- parsePmt(payload.subarray(offset), psi);
- }
- };
- parsePat = function(payload, pat) {
- pat.section_number = payload[7];
- pat.last_section_number = payload[8];
- // skip the PSI header and parse the first PMT entry
- self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
- pat.pmtPid = self.pmtPid;
- };
- /**
- * Parse out the relevant fields of a Program Map Table (PMT).
- * @param payload {Uint8Array} the PMT-specific portion of an MP2T
- * packet. The first byte in this array should be the table_id
- * field.
- * @param pmt {object} the object that should be decorated with
- * fields parsed from the PMT.
- */
- parsePmt = function(payload, pmt) {
- var sectionLength, tableEnd, programInfoLength, offset;
- // PMTs can be sent ahead of the time when they should actually
- // take effect. We don't believe this should ever be the case
- // for HLS but we'll ignore "forward" PMT declarations if we see
- // them. Future PMT declarations have the current_next_indicator
- // set to zero.
- if (!(payload[5] & 0x01)) {
- return;
- }
- // overwrite any existing program map table
- self.programMapTable = {};
- // the mapping table ends at the end of the current section
- sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
- tableEnd = 3 + sectionLength - 4;
- // to determine where the table is, we have to figure out how
- // long the program info descriptors are
- programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
- // advance the offset to the first entry in the mapping table
- offset = 12 + programInfoLength;
- while (offset < tableEnd) {
- // add an entry that maps the elementary_pid to the stream_type
- self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset];
- // move to the next table entry
- // skip past the elementary stream descriptors, if present
- offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
- }
- // record the map on the packet as well
- pmt.programMapTable = self.programMapTable;
- // if there are any packets waiting for a PMT to be found, process them now
- while (self.packetsWaitingForPmt.length) {
- self.processPes_.apply(self, self.packetsWaitingForPmt.shift());
- }
- };
- /**
- * Deliver a new MP2T packet to the stream.
- */
- this.push = function(packet) {
- var
- result = {},
- offset = 4;
- result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
- // pid is a 13-bit field starting at the last bit of packet[1]
- result.pid = packet[1] & 0x1f;
- result.pid <<= 8;
- result.pid |= packet[2];
- // if an adaption field is present, its length is specified by the
- // fifth byte of the TS packet header. The adaptation field is
- // used to add stuffing to PES packets that don't fill a complete
- // TS packet, and to specify some forms of timing and control data
- // that we do not currently use.
- if (((packet[3] & 0x30) >>> 4) > 0x01) {
- offset += packet[offset] + 1;
- }
- // parse the rest of the packet based on the type
- if (result.pid === 0) {
- result.type = 'pat';
- parsePsi(packet.subarray(offset), result);
- this.trigger('data', result);
- } else if (result.pid === this.pmtPid) {
- result.type = 'pmt';
- parsePsi(packet.subarray(offset), result);
- this.trigger('data', result);
- } else if (this.programMapTable === undefined) {
- // When we have not seen a PMT yet, defer further processing of
- // PES packets until one has been parsed
- this.packetsWaitingForPmt.push([packet, offset, result]);
- } else {
- this.processPes_(packet, offset, result);
- }
- };
- this.processPes_ = function (packet, offset, result) {
- result.streamType = this.programMapTable[result.pid];
- result.type = 'pes';
- result.data = packet.subarray(offset);
- this.trigger('data', result);
- };
- };
- TransportParseStream.prototype = new Stream();
- TransportParseStream.STREAM_TYPES = {
- h264: 0x1b,
- adts: 0x0f
- };
- /**
- * Reconsistutes program elementary stream (PES) packets from parsed
- * transport stream packets. That is, if you pipe an
- * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
- * events will be events which capture the bytes for individual PES
- * packets plus relevant metadata that has been extracted from the
- * container.
- */
- ElementaryStream = function() {
- var
- // PES packet fragments
- video = {
- data: [],
- size: 0
- },
- audio = {
- data: [],
- size: 0
- },
- timedMetadata = {
- data: [],
- size: 0
- },
- parsePes = function(payload, pes) {
- var ptsDtsFlags;
- // find out if this packets starts a new keyframe
- pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
- // PES packets may be annotated with a PTS value, or a PTS value
- // and a DTS value. Determine what combination of values is
- // available to work with.
- ptsDtsFlags = payload[7];
- // PTS and DTS are normally stored as a 33-bit number. Javascript
- // performs all bitwise operations on 32-bit integers but javascript
- // supports a much greater range (52-bits) of integer using standard
- // mathematical operations.
- // We construct a 31-bit value using bitwise operators over the 31
- // most significant bits and then multiply by 4 (equal to a left-shift
- // of 2) before we add the final 2 least significant bits of the
- // timestamp (equal to an OR.)
- if (ptsDtsFlags & 0xC0) {
- // the PTS and DTS are not written out directly. For information
- // on how they are encoded, see
- // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
- pes.pts = (payload[9] & 0x0E) << 27
- | (payload[10] & 0xFF) << 20
- | (payload[11] & 0xFE) << 12
- | (payload[12] & 0xFF) << 5
- | (payload[13] & 0xFE) >>> 3;
- pes.pts *= 4; // Left shift by 2
- pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
- pes.dts = pes.pts;
- if (ptsDtsFlags & 0x40) {
- pes.dts = (payload[14] & 0x0E ) << 27
- | (payload[15] & 0xFF ) << 20
- | (payload[16] & 0xFE ) << 12
- | (payload[17] & 0xFF ) << 5
- | (payload[18] & 0xFE ) >>> 3;
- pes.dts *= 4; // Left shift by 2
- pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
- }
- }
- // the data section starts immediately after the PES header.
- // pes_header_data_length specifies the number of header bytes
- // that follow the last byte of the field.
- pes.data = payload.subarray(9 + payload[8]);
- },
- flushStream = function(stream, type) {
- var
- packetData = new Uint8Array(stream.size),
- event = {
- type: type
- },
- i = 0,
- fragment;
- // do nothing if there is no buffered data
- if (!stream.data.length) {
- return;
- }
- event.trackId = stream.data[0].pid;
- // reassemble the packet
- while (stream.data.length) {
- fragment = stream.data.shift();
- packetData.set(fragment.data, i);
- i += fragment.data.byteLength;
- }
- // parse assembled packet's PES header
- parsePes(packetData, event);
- stream.size = 0;
- self.trigger('data', event);
- },
- self;
- ElementaryStream.prototype.init.call(this);
- self = this;
- this.push = function(data) {
- ({
- pat: function() {
- // we have to wait for the PMT to arrive as well before we
- // have any meaningful metadata
- },
- pes: function() {
- var stream, streamType;
- switch (data.streamType) {
- case StreamTypes.H264_STREAM_TYPE:
- case m2tsStreamTypes.H264_STREAM_TYPE:
- stream = video;
- streamType = 'video';
- break;
- case StreamTypes.ADTS_STREAM_TYPE:
- stream = audio;
- streamType = 'audio';
- break;
- case StreamTypes.METADATA_STREAM_TYPE:
- stream = timedMetadata;
- streamType = 'timed-metadata';
- break;
- default:
- // ignore unknown stream types
- return;
- }
- // if a new packet is starting, we can flush the completed
- // packet
- if (data.payloadUnitStartIndicator) {
- flushStream(stream, streamType);
- }
- // buffer this fragment until we are sure we've received the
- // complete payload
- stream.data.push(data);
- stream.size += data.data.byteLength;
- },
- pmt: function() {
- var
- event = {
- type: 'metadata',
- tracks: []
- },
- programMapTable = data.programMapTable,
- k,
- track;
- // translate streams to tracks
- for (k in programMapTable) {
- if (programMapTable.hasOwnProperty(k)) {
- track = {
- timelineStartInfo: {
- baseMediaDecodeTime: 0
- }
- };
- track.id = +k;
- if (programMapTable[k] === m2tsStreamTypes.H264_STREAM_TYPE) {
- track.codec = 'avc';
- track.type = 'video';
- } else if (programMapTable[k] === m2tsStreamTypes.ADTS_STREAM_TYPE) {
- track.codec = 'adts';
- track.type = 'audio';
- }
- event.tracks.push(track);
- }
- }
- self.trigger('data', event);
- }
- })[data.type]();
- };
- /**
- * Flush any remaining input. Video PES packets may be of variable
- * length. Normally, the start of a new video packet can trigger the
- * finalization of the previous packet. That is not possible if no
- * more video is forthcoming, however. In that case, some other
- * mechanism (like the end of the file) has to be employed. When it is
- * clear that no additional data is forthcoming, calling this method
- * will flush the buffered packets.
- */
- this.flush = function() {
- // !!THIS ORDER IS IMPORTANT!!
- // video first then audio
- flushStream(video, 'video');
- flushStream(audio, 'audio');
- flushStream(timedMetadata, 'timed-metadata');
- this.trigger('done');
- };
- };
- ElementaryStream.prototype = new Stream();
- var m2ts = {
- PAT_PID: 0x0000,
- MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
- TransportPacketStream: TransportPacketStream,
- TransportParseStream: TransportParseStream,
- ElementaryStream: ElementaryStream,
- CaptionStream: CaptionStream.CaptionStream,
- Cea608Stream: CaptionStream.Cea608Stream,
- MetadataStream: require('./metadata-stream'),
- };
- for (var type in StreamTypes) {
- if (StreamTypes.hasOwnProperty(type)) {
- m2ts[type] = StreamTypes[type];
- }
- }
- module.exports = m2ts;
- },{"../utils/stream.js":55,"./caption-stream":44,"./metadata-stream":47,"./stream-types":48,"./stream-types.js":48}],47:[function(require,module,exports){
- /**
- * Accepts program elementary stream (PES) data events and parses out
- * ID3 metadata from them, if present.
- * @see http://id3.org/id3v2.3.0
- */
- 'use strict';
- var
- Stream = require('../utils/stream'),
- StreamTypes = require('./stream-types'),
- // return a percent-encoded representation of the specified byte range
- // @see http://en.wikipedia.org/wiki/Percent-encoding
- percentEncode = function(bytes, start, end) {
- var i, result = '';
- for (i = start; i < end; i++) {
- result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
- }
- return result;
- },
- // return the string representation of the specified byte range,
- // interpreted as UTf-8.
- parseUtf8 = function(bytes, start, end) {
- return decodeURIComponent(percentEncode(bytes, start, end));
- },
- // return the string representation of the specified byte range,
- // interpreted as ISO-8859-1.
- parseIso88591 = function(bytes, start, end) {
- return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
- },
- parseSyncSafeInteger = function (data) {
- return (data[0] << 21) |
- (data[1] << 14) |
- (data[2] << 7) |
- (data[3]);
- },
- tagParsers = {
- 'TXXX': function(tag) {
- var i;
- if (tag.data[0] !== 3) {
- // ignore frames with unrecognized character encodings
- return;
- }
- for (i = 1; i < tag.data.length; i++) {
- if (tag.data[i] === 0) {
- // parse the text fields
- tag.description = parseUtf8(tag.data, 1, i);
- // do not include the null terminator in the tag value
- tag.value = parseUtf8(tag.data, i + 1, tag.data.length - 1);
- break;
- }
- }
- tag.data = tag.value;
- },
- 'WXXX': function(tag) {
- var i;
- if (tag.data[0] !== 3) {
- // ignore frames with unrecognized character encodings
- return;
- }
- for (i = 1; i < tag.data.length; i++) {
- if (tag.data[i] === 0) {
- // parse the description and URL fields
- tag.description = parseUtf8(tag.data, 1, i);
- tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
- break;
- }
- }
- },
- 'PRIV': function(tag) {
- var i;
- for (i = 0; i < tag.data.length; i++) {
- if (tag.data[i] === 0) {
- // parse the description and URL fields
- tag.owner = parseIso88591(tag.data, 0, i);
- break;
- }
- }
- tag.privateData = tag.data.subarray(i + 1);
- tag.data = tag.privateData;
- }
- },
- MetadataStream;
- MetadataStream = function(options) {
- var
- settings = {
- debug: !!(options && options.debug),
- // the bytes of the program-level descriptor field in MP2T
- // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
- // program element descriptors"
- descriptor: options && options.descriptor
- },
- // the total size in bytes of the ID3 tag being parsed
- tagSize = 0,
- // tag data that is not complete enough to be parsed
- buffer = [],
- // the total number of bytes currently in the buffer
- bufferSize = 0,
- i;
- MetadataStream.prototype.init.call(this);
- // calculate the text track in-band metadata track dispatch type
- // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
- this.dispatchType = StreamTypes.METADATA_STREAM_TYPE.toString(16);
- if (settings.descriptor) {
- for (i = 0; i < settings.descriptor.length; i++) {
- this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
- }
- }
- this.push = function(chunk) {
- var tag, frameStart, frameSize, frame, i, frameHeader;
- if (chunk.type !== 'timed-metadata') {
- return;
- }
- // if data_alignment_indicator is set in the PES header,
- // we must have the start of a new ID3 tag. Assume anything
- // remaining in the buffer was malformed and throw it out
- if (chunk.dataAlignmentIndicator) {
- bufferSize = 0;
- buffer.length = 0;
- }
- // ignore events that don't look like ID3 data
- 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))) {
- if (settings.debug) {
- console.log('Skipping unrecognized metadata packet');
- }
- return;
- }
- // add this chunk to the data we've collected so far
- buffer.push(chunk);
- bufferSize += chunk.data.byteLength;
- // grab the size of the entire frame from the ID3 header
- if (buffer.length === 1) {
- // the frame size is transmitted as a 28-bit integer in the
- // last four bytes of the ID3 header.
- // The most significant bit of each byte is dropped and the
- // results concatenated to recover the actual value.
- tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10));
- // ID3 reports the tag size excluding the header but it's more
- // convenient for our comparisons to include it
- tagSize += 10;
- }
- // if the entire frame has not arrived, wait for more data
- if (bufferSize < tagSize) {
- return;
- }
- // collect the entire frame so it can be parsed
- tag = {
- data: new Uint8Array(tagSize),
- frames: [],
- pts: buffer[0].pts,
- dts: buffer[0].dts
- };
- for (i = 0; i < tagSize;) {
- tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
- i += buffer[0].data.byteLength;
- bufferSize -= buffer[0].data.byteLength;
- buffer.shift();
- }
- // find the start of the first frame and the end of the tag
- frameStart = 10;
- if (tag.data[5] & 0x40) {
- // advance the frame start past the extended header
- frameStart += 4; // header size field
- frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14));
- // clip any padding off the end
- tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
- }
- // parse one or more ID3 frames
- // http://id3.org/id3v2.3.0#ID3v2_frame_overview
- do {
- // determine the number of bytes in this frame
- frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
- if (frameSize < 1) {
- return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
- }
- frameHeader = String.fromCharCode(tag.data[frameStart],
- tag.data[frameStart + 1],
- tag.data[frameStart + 2],
- tag.data[frameStart + 3]);
- frame = {
- id: frameHeader,
- data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
- };
- frame.key = frame.id;
- if (tagParsers[frame.id]) {
- tagParsers[frame.id](frame);
- if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
- var
- d = frame.data,
- size = ((d[3] & 0x01) << 30) |
- (d[4] << 22) |
- (d[5] << 14) |
- (d[6] << 6) |
- (d[7] >>> 2);
- size *= 4;
- size += d[7] & 0x03;
- frame.timeStamp = size;
- this.trigger('timestamp', frame);
- }
- }
- tag.frames.push(frame);
- frameStart += 10; // advance past the frame header
- frameStart += frameSize; // advance past the frame body
- } while (frameStart < tagSize);
- this.trigger('data', tag);
- };
- };
- MetadataStream.prototype = new Stream();
- module.exports = MetadataStream;
- },{"../utils/stream":55,"./stream-types":48}],48:[function(require,module,exports){
- 'use strict';
- module.exports = {
- H264_STREAM_TYPE: 0x1B,
- ADTS_STREAM_TYPE: 0x0F,
- METADATA_STREAM_TYPE: 0x15
- };
- },{}],49:[function(require,module,exports){
- module.exports = {
- generator: require('./mp4-generator'),
- Transmuxer: require('./transmuxer').Transmuxer,
- AudioSegmentStream: require('./transmuxer').AudioSegmentStream,
- VideoSegmentStream: require('./transmuxer').VideoSegmentStream,
- tools: require('../tools/mp4-inspector'),
- };
- },{"../tools/mp4-inspector":53,"./mp4-generator":50,"./transmuxer":51}],50:[function(require,module,exports){
- /**
- * mux.js
- *
- * Copyright (c) 2015 Brightcove
- * All rights reserved.
- *
- * Functions that generate fragmented MP4s suitable for use with Media
- * Source Extensions.
- */
- 'use strict';
- var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak,
- tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, styp, 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
- (function() {
- var i;
- types = {
- avc1: [], // codingname
- avcC: [],
- btrt: [],
- dinf: [],
- dref: [],
- esds: [],
- ftyp: [],
- hdlr: [],
- mdat: [],
- mdhd: [],
- mdia: [],
- mfhd: [],
- minf: [],
- moof: [],
- moov: [],
- mp4a: [], // codingname
- mvex: [],
- mvhd: [],
- sdtp: [],
- smhd: [],
- stbl: [],
- stco: [],
- stsc: [],
- stsd: [],
- stsz: [],
- stts: [],
- styp: [],
- tfdt: [],
- tfhd: [],
- traf: [],
- trak: [],
- trun: [],
- trex: [],
- tkhd: [],
- vmhd: []
- };
- // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
- // don't throw an error
- if (typeof Uint8Array === 'undefined') {
- return;
- }
- for (i in types) {
- if (types.hasOwnProperty(i)) {
- types[i] = [
- i.charCodeAt(0),
- i.charCodeAt(1),
- i.charCodeAt(2),
- i.charCodeAt(3)
- ];
- }
- }
- MAJOR_BRAND = new Uint8Array([
- 'i'.charCodeAt(0),
- 's'.charCodeAt(0),
- 'o'.charCodeAt(0),
- 'm'.charCodeAt(0)
- ]);
- AVC1_BRAND = new Uint8Array([
- 'a'.charCodeAt(0),
- 'v'.charCodeAt(0),
- 'c'.charCodeAt(0),
- '1'.charCodeAt(0)
- ]);
- MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
- VIDEO_HDLR = new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, 0x00, 0x00, // pre_defined
- 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x56, 0x69, 0x64, 0x65,
- 0x6f, 0x48, 0x61, 0x6e,
- 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
- ]);
- AUDIO_HDLR = new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, 0x00, 0x00, // pre_defined
- 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x53, 0x6f, 0x75, 0x6e,
- 0x64, 0x48, 0x61, 0x6e,
- 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
- ]);
- HDLR_TYPES = {
- "video":VIDEO_HDLR,
- "audio": AUDIO_HDLR
- };
- DREF = new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, 0x00, 0x01, // entry_count
- 0x00, 0x00, 0x00, 0x0c, // entry_size
- 0x75, 0x72, 0x6c, 0x20, // 'url' type
- 0x00, // version 0
- 0x00, 0x00, 0x01 // entry_flags
- ]);
- SMHD = new Uint8Array([
- 0x00, // version
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, // balance, 0 means centered
- 0x00, 0x00 // reserved
- ]);
- STCO = new Uint8Array([
- 0x00, // version
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, 0x00, 0x00 // entry_count
- ]);
- STSC = STCO;
- STSZ = new Uint8Array([
- 0x00, // version
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, 0x00, 0x00, // sample_size
- 0x00, 0x00, 0x00, 0x00, // sample_count
- ]);
- STTS = STCO;
- VMHD = new Uint8Array([
- 0x00, // version
- 0x00, 0x00, 0x01, // flags
- 0x00, 0x00, // graphicsmode
- 0x00, 0x00,
- 0x00, 0x00,
- 0x00, 0x00 // opcolor
- ]);
- })();
- box = function(type) {
- var
- payload = [],
- size = 0,
- i,
- result,
- view;
- for (i = 1; i < arguments.length; i++) {
- payload.push(arguments[i]);
- }
- i = payload.length;
- // calculate the total size we need to allocate
- while (i--) {
- size += payload[i].byteLength;
- }
- result = new Uint8Array(size + 8);
- view = new DataView(result.buffer, result.byteOffset, result.byteLength);
- view.setUint32(0, result.byteLength);
- result.set(type, 4);
- // copy the payload into the result
- for (i = 0, size = 8; i < payload.length; i++) {
- result.set(payload[i], size);
- size += payload[i].byteLength;
- }
- return result;
- };
- dinf = function() {
- return box(types.dinf, box(types.dref, DREF));
- };
- esds = function(track) {
- return box(types.esds, new Uint8Array([
- 0x00, // version
- 0x00, 0x00, 0x00, // flags
- // ES_Descriptor
- 0x03, // tag, ES_DescrTag
- 0x19, // length
- 0x00, 0x00, // ES_ID
- 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
- // DecoderConfigDescriptor
- 0x04, // tag, DecoderConfigDescrTag
- 0x11, // length
- 0x40, // object type
- 0x15, // streamType
- 0x00, 0x06, 0x00, // bufferSizeDB
- 0x00, 0x00, 0xda, 0xc0, // maxBitrate
- 0x00, 0x00, 0xda, 0xc0, // avgBitrate
- // DecoderSpecificInfo
- 0x05, // tag, DecoderSpecificInfoTag
- 0x02, // length
- // ISO/IEC 14496-3, AudioSpecificConfig
- // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
- (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1),
- (track.samplingfrequencyindex << 7) | (track.channelcount << 3),
- 0x06, 0x01, 0x02 // GASpecificConfig
- ]));
- };
- ftyp = function() {
- return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
- };
- hdlr = function(type) {
- return box(types.hdlr, HDLR_TYPES[type]);
- };
- mdat = function(data) {
- return box(types.mdat, data);
- };
- mdhd = function(track) {
- var result = new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, 0x00, 0x02, // creation_time
- 0x00, 0x00, 0x00, 0x03, // modification_time
- 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
- (track.duration >>> 24) & 0xFF,
- (track.duration >>> 16) & 0xFF,
- (track.duration >>> 8) & 0xFF,
- track.duration & 0xFF, // duration
- 0x55, 0xc4, // 'und' language (undetermined)
- 0x00, 0x00
- ]);
- // Use the sample rate from the track metadata, when it is
- // defined. The sample rate can be parsed out of an ADTS header, for
- // instance.
- if (track.samplerate) {
- result[12] = (track.samplerate >>> 24) & 0xFF;
- result[13] = (track.samplerate >>> 16) & 0xFF;
- result[14] = (track.samplerate >>> 8) & 0xFF;
- result[15] = (track.samplerate) & 0xFF;
- }
- return box(types.mdhd, result);
- };
- mdia = function(track) {
- return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
- };
- mfhd = function(sequenceNumber) {
- return box(types.mfhd, new Uint8Array([
- 0x00,
- 0x00, 0x00, 0x00, // flags
- (sequenceNumber & 0xFF000000) >> 24,
- (sequenceNumber & 0xFF0000) >> 16,
- (sequenceNumber & 0xFF00) >> 8,
- sequenceNumber & 0xFF, // sequence_number
- ]));
- };
- minf = function(track) {
- return box(types.minf,
- track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD),
- dinf(),
- stbl(track));
- };
- moof = function(sequenceNumber, tracks) {
- var
- trackFragments = [],
- i = tracks.length;
- // build traf boxes for each track fragment
- while (i--) {
- trackFragments[i] = traf(tracks[i]);
- }
- return box.apply(null, [
- types.moof,
- mfhd(sequenceNumber)
- ].concat(trackFragments));
- };
- /**
- * Returns a movie box.
- * @param tracks {array} the tracks associated with this movie
- * @see ISO/IEC 14496-12:2012(E), section 8.2.1
- */
- moov = function(tracks) {
- var
- i = tracks.length,
- boxes = [];
- while (i--) {
- boxes[i] = trak(tracks[i]);
- }
- return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
- };
- mvex = function(tracks) {
- var
- i = tracks.length,
- boxes = [];
- while (i--) {
- boxes[i] = trex(tracks[i]);
- }
- return box.apply(null, [types.mvex].concat(boxes));
- };
- mvhd = function(duration) {
- var
- bytes = new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, 0x00, 0x01, // creation_time
- 0x00, 0x00, 0x00, 0x02, // modification_time
- 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
- (duration & 0xFF000000) >> 24,
- (duration & 0xFF0000) >> 16,
- (duration & 0xFF00) >> 8,
- duration & 0xFF, // duration
- 0x00, 0x01, 0x00, 0x00, // 1.0 rate
- 0x01, 0x00, // 1.0 volume
- 0x00, 0x00, // reserved
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x00, 0x00, 0x00, 0x00, // reserved
- 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
- 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
- 0xff, 0xff, 0xff, 0xff // next_track_ID
- ]);
- return box(types.mvhd, bytes);
- };
- sdtp = function(track) {
- var
- samples = track.samples || [],
- bytes = new Uint8Array(4 + samples.length),
- flags,
- i;
- // leave the full box header (4 bytes) all zero
- // write the sample table
- for (i = 0; i < samples.length; i++) {
- flags = samples[i].flags;
- bytes[i + 4] = (flags.dependsOn << 4) |
- (flags.isDependedOn << 2) |
- (flags.hasRedundancy);
- }
- return box(types.sdtp,
- bytes);
- };
- stbl = function(track) {
- return box(types.stbl,
- stsd(track),
- box(types.stts, STTS),
- box(types.stsc, STSC),
- box(types.stsz, STSZ),
- box(types.stco, STCO));
- };
- (function() {
- var videoSample, audioSample;
- stsd = function(track) {
- return box(types.stsd, new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x00, // flags
- 0x00, 0x00, 0x00, 0x01
- ]), track.type === 'video' ? videoSample(track) : audioSample(track));
- };
- videoSample = function(track) {
- var
- sps = track.sps || [],
- pps = track.pps || [],
- sequenceParameterSets = [],
- pictureParameterSets = [],
- i;
- // assemble the SPSs
- for (i = 0; i < sps.length; i++) {
- sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
- sequenceParameterSets.push((sps[i].byteLength & 0xFF)); // sequenceParameterSetLength
- sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
- }
- // assemble the PPSs
- for (i = 0; i < pps.length; i++) {
- pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
- pictureParameterSets.push((pps[i].byteLength & 0xFF));
- pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
- }
- return box(types.avc1, new Uint8Array([
- 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, // reserved
- 0x00, 0x01, // data_reference_index
- 0x00, 0x00, // pre_defined
- 0x00, 0x00, // reserved
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, // pre_defined
- (track.width & 0xff00) >> 8,
- track.width & 0xff, // width
- (track.height & 0xff00) >> 8,
- track.height & 0xff, // height
- 0x00, 0x48, 0x00, 0x00, // horizresolution
- 0x00, 0x48, 0x00, 0x00, // vertresolution
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x00, 0x01, // frame_count
- 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
- 0x00, 0x18, // depth = 24
- 0x11, 0x11 // pre_defined = -1
- ]), box(types.avcC, new Uint8Array([
- 0x01, // configurationVersion
- track.profileIdc, // AVCProfileIndication
- track.profileCompatibility, // profile_compatibility
- track.levelIdc, // AVCLevelIndication
- 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
- ].concat([
- sps.length // numOfSequenceParameterSets
- ]).concat(sequenceParameterSets).concat([
- pps.length // numOfPictureParameterSets
- ]).concat(pictureParameterSets))), // "PPS"
- box(types.btrt, new Uint8Array([
- 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
- 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
- 0x00, 0x2d, 0xc6, 0xc0
- ])) // avgBitrate
- );
- };
- audioSample = function(track) {
- return box(types.mp4a, new Uint8Array([
- // SampleEntry, ISO/IEC 14496-12
- 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, // reserved
- 0x00, 0x01, // data_reference_index
- // AudioSampleEntry, ISO/IEC 14496-12
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x00, 0x00, 0x00, 0x00, // reserved
- (track.channelcount & 0xff00) >> 8,
- (track.channelcount & 0xff), // channelcount
- (track.samplesize & 0xff00) >> 8,
- (track.samplesize & 0xff), // samplesize
- 0x00, 0x00, // pre_defined
- 0x00, 0x00, // reserved
- (track.samplerate & 0xff00) >> 8,
- (track.samplerate & 0xff),
- 0x00, 0x00 // samplerate, 16.16
- // MP4AudioSampleEntry, ISO/IEC 14496-14
- ]), esds(track));
- };
- })();
- styp = function() {
- return box(types.styp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND);
- };
- tkhd = function(track) {
- var result = new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x07, // flags
- 0x00, 0x00, 0x00, 0x00, // creation_time
- 0x00, 0x00, 0x00, 0x00, // modification_time
- (track.id & 0xFF000000) >> 24,
- (track.id & 0xFF0000) >> 16,
- (track.id & 0xFF00) >> 8,
- track.id & 0xFF, // track_ID
- 0x00, 0x00, 0x00, 0x00, // reserved
- (track.duration & 0xFF000000) >> 24,
- (track.duration & 0xFF0000) >> 16,
- (track.duration & 0xFF00) >> 8,
- track.duration & 0xFF, // duration
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, // reserved
- 0x00, 0x00, // layer
- 0x00, 0x00, // alternate_group
- 0x01, 0x00, // non-audio track volume
- 0x00, 0x00, // reserved
- 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
- (track.width & 0xFF00) >> 8,
- track.width & 0xFF,
- 0x00, 0x00, // width
- (track.height & 0xFF00) >> 8,
- track.height & 0xFF,
- 0x00, 0x00 // height
- ]);
- return box(types.tkhd, result);
- };
- /**
- * Generate a track fragment (traf) box. A traf box collects metadata
- * about tracks in a movie fragment (moof) box.
- */
- traf = function(track) {
- var trackFragmentHeader, trackFragmentDecodeTime,
- trackFragmentRun, sampleDependencyTable, dataOffset;
- trackFragmentHeader = box(types.tfhd, new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x3a, // flags
- (track.id & 0xFF000000) >> 24,
- (track.id & 0xFF0000) >> 16,
- (track.id & 0xFF00) >> 8,
- (track.id & 0xFF), // track_ID
- 0x00, 0x00, 0x00, 0x01, // sample_description_index
- 0x00, 0x00, 0x00, 0x00, // default_sample_duration
- 0x00, 0x00, 0x00, 0x00, // default_sample_size
- 0x00, 0x00, 0x00, 0x00 // default_sample_flags
- ]));
- trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x00, // flags
- // baseMediaDecodeTime
- (track.baseMediaDecodeTime >>> 24) & 0xFF,
- (track.baseMediaDecodeTime >>> 16) & 0xFF,
- (track.baseMediaDecodeTime >>> 8) & 0xFF,
- track.baseMediaDecodeTime & 0xFF
- ]));
- // the data offset specifies the number of bytes from the start of
- // the containing moof to the first payload byte of the associated
- // mdat
- dataOffset = (32 + // tfhd
- 16 + // tfdt
- 8 + // traf header
- 16 + // mfhd
- 8 + // moof header
- 8); // mdat header
- // audio tracks require less metadata
- if (track.type === 'audio') {
- trackFragmentRun = trun(track, dataOffset);
- return box(types.traf,
- trackFragmentHeader,
- trackFragmentDecodeTime,
- trackFragmentRun);
- }
- // video tracks should contain an independent and disposable samples
- // box (sdtp)
- // generate one and adjust offsets to match
- sampleDependencyTable = sdtp(track);
- trackFragmentRun = trun(track,
- sampleDependencyTable.length + dataOffset);
- return box(types.traf,
- trackFragmentHeader,
- trackFragmentDecodeTime,
- trackFragmentRun,
- sampleDependencyTable);
- };
- /**
- * Generate a track box.
- * @param track {object} a track definition
- * @return {Uint8Array} the track box
- */
- trak = function(track) {
- track.duration = track.duration || 0xffffffff;
- return box(types.trak,
- tkhd(track),
- mdia(track));
- };
- trex = function(track) {
- var result = new Uint8Array([
- 0x00, // version 0
- 0x00, 0x00, 0x00, // flags
- (track.id & 0xFF000000) >> 24,
- (track.id & 0xFF0000) >> 16,
- (track.id & 0xFF00) >> 8,
- (track.id & 0xFF), // track_ID
- 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
- 0x00, 0x00, 0x00, 0x00, // default_sample_duration
- 0x00, 0x00, 0x00, 0x00, // default_sample_size
- 0x00, 0x01, 0x00, 0x01 // default_sample_flags
- ]);
- // the last two bytes of default_sample_flags is the sample
- // degradation priority, a hint about the importance of this sample
- // relative to others. Lower the degradation priority for all sample
- // types other than video.
- if (track.type !== 'video') {
- result[result.length - 1] = 0x00;
- }
- return box(types.trex, result);
- };
- (function() {
- var audioTrun, videoTrun, trunHeader;
- // This method assumes all samples are uniform. That is, if a
- // duration is present for the first sample, it will be present for
- // all subsequent samples.
- // see ISO/IEC 14496-12:2012, Section 8.8.8.1
- trunHeader = function(samples, offset) {
- var durationPresent = 0, sizePresent = 0,
- flagsPresent = 0, compositionTimeOffset = 0;
- // trun flag constants
- if (samples.length) {
- if (samples[0].duration !== undefined) {
- durationPresent = 0x1;
- }
- if (samples[0].size !== undefined) {
- sizePresent = 0x2;
- }
- if (samples[0].flags !== undefined) {
- flagsPresent = 0x4;
- }
- if (samples[0].compositionTimeOffset !== undefined) {
- compositionTimeOffset = 0x8;
- }
- }
- return [
- 0x00, // version 0
- 0x00,
- durationPresent | sizePresent | flagsPresent | compositionTimeOffset,
- 0x01, // flags
- (samples.length & 0xFF000000) >>> 24,
- (samples.length & 0xFF0000) >>> 16,
- (samples.length & 0xFF00) >>> 8,
- samples.length & 0xFF, // sample_count
- (offset & 0xFF000000) >>> 24,
- (offset & 0xFF0000) >>> 16,
- (offset & 0xFF00) >>> 8,
- offset & 0xFF // data_offset
- ];
- };
- videoTrun = function(track, offset) {
- var bytes, samples, sample, i;
- samples = track.samples || [];
- offset += 8 + 12 + (16 * samples.length);
- bytes = trunHeader(samples, offset);
- for (i = 0; i < samples.length; i++) {
- sample = samples[i];
- bytes = bytes.concat([
- (sample.duration & 0xFF000000) >>> 24,
- (sample.duration & 0xFF0000) >>> 16,
- (sample.duration & 0xFF00) >>> 8,
- sample.duration & 0xFF, // sample_duration
- (sample.size & 0xFF000000) >>> 24,
- (sample.size & 0xFF0000) >>> 16,
- (sample.size & 0xFF00) >>> 8,
- sample.size & 0xFF, // sample_size
- (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
- (sample.compositionTimeOffset & 0xFF000000) >>> 24,
- (sample.compositionTimeOffset & 0xFF0000) >>> 16,
- (sample.compositionTimeOffset & 0xFF00) >>> 8,
- sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
- ]);
- }
- return box(types.trun, new Uint8Array(bytes));
- };
- audioTrun = function(track, offset) {
- var bytes, samples, sample, i;
- samples = track.samples || [];
- offset += 8 + 12 + (8 * samples.length);
- bytes = trunHeader(samples, offset);
- for (i = 0; i < samples.length; i++) {
- sample = samples[i];
- bytes = bytes.concat([
- (sample.duration & 0xFF000000) >>> 24,
- (sample.duration & 0xFF0000) >>> 16,
- (sample.duration & 0xFF00) >>> 8,
- sample.duration & 0xFF, // sample_duration
- (sample.size & 0xFF000000) >>> 24,
- (sample.size & 0xFF0000) >>> 16,
- (sample.size & 0xFF00) >>> 8,
- sample.size & 0xFF]); // sample_size
- }
- return box(types.trun, new Uint8Array(bytes));
- };
- trun = function(track, offset) {
- if (track.type === 'audio') {
- return audioTrun(track, offset);
- } else {
- return videoTrun(track, offset);
- }
- };
- })();
- module.exports = {
- ftyp: ftyp,
- mdat: mdat,
- moof: moof,
- moov: moov,
- initSegment: function(tracks) {
- var
- fileType = ftyp(),
- movie = moov(tracks),
- result;
- result = new Uint8Array(fileType.byteLength + movie.byteLength);
- result.set(fileType);
- result.set(movie, fileType.byteLength);
- return result;
- }
- };
- },{}],51:[function(require,module,exports){
- /**
- * mux.js
- *
- * Copyright (c) 2015 Brightcove
- * All rights reserved.
- *
- * A stream-based mp2t to mp4 converter. This utility can be used to
- * deliver mp4s to a SourceBuffer on platforms that support native
- * Media Source Extensions.
- */
- 'use strict';
- var Stream = require('../utils/stream.js');
- var mp4 = require('./mp4-generator.js');
- var m2ts = require('../m2ts/m2ts.js');
- var AdtsStream = require('../codecs/adts.js');
- var H264Stream = require('../codecs/h264').H264Stream;
- var AacStream = require('../aac');
- // constants
- var AUDIO_PROPERTIES = [
- 'audioobjecttype',
- 'channelcount',
- 'samplerate',
- 'samplingfrequencyindex',
- 'samplesize'
- ];
- var VIDEO_PROPERTIES = [
- 'width',
- 'height',
- 'profileIdc',
- 'levelIdc',
- 'profileCompatibility',
- ];
- // object types
- var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
- // Helper functions
- var
- createDefaultSample,
- isLikelyAacData,
- collectDtsInfo,
- clearDtsInfo,
- calculateTrackBaseMediaDecodeTime,
- arrayEquals,
- sumFrameByteLengths;
- /**
- * Default sample object
- * see ISO/IEC 14496-12:2012, section 8.6.4.3
- */
- createDefaultSample = function () {
- return {
- size: 0,
- flags: {
- isLeading: 0,
- dependsOn: 1,
- isDependedOn: 0,
- hasRedundancy: 0,
- degradationPriority: 0
- }
- };
- };
- isLikelyAacData = function (data) {
- if ((data[0] === 'I'.charCodeAt(0)) &&
- (data[1] === 'D'.charCodeAt(0)) &&
- (data[2] === '3'.charCodeAt(0))) {
- return true;
- }
- return false;
- };
- /**
- * Compare two arrays (even typed) for same-ness
- */
- arrayEquals = function(a, b) {
- var
- i;
- if (a.length !== b.length) {
- return false;
- }
- // compare the value of each element in the array
- for (i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) {
- return false;
- }
- }
- return true;
- };
- /**
- * Sum the `byteLength` properties of the data in each AAC frame
- */
- sumFrameByteLengths = function(array) {
- var
- i,
- currentObj,
- sum = 0;
- // sum the byteLength's all each nal unit in the frame
- for (i = 0; i < array.length; i++) {
- currentObj = array[i];
- sum += currentObj.data.byteLength;
- }
- return sum;
- };
- /**
- * Constructs a single-track, ISO BMFF media segment from AAC data
- * events. The output of this stream can be fed to a SourceBuffer
- * configured with a suitable initialization segment.
- */
- AudioSegmentStream = function(track) {
- var
- adtsFrames = [],
- sequenceNumber = 0,
- earliestAllowedDts = 0;
- AudioSegmentStream.prototype.init.call(this);
- this.push = function(data) {
- collectDtsInfo(track, data);
- if (track) {
- AUDIO_PROPERTIES.forEach(function(prop) {
- track[prop] = data[prop];
- });
- }
- // buffer audio data until end() is called
- adtsFrames.push(data);
- };
- this.setEarliestDts = function(earliestDts) {
- earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
- };
- this.flush = function() {
- var
- frames,
- moof,
- mdat,
- boxes;
- // return early if no audio data has been observed
- if (adtsFrames.length === 0) {
- this.trigger('done', 'AudioSegmentStream');
- return;
- }
- frames = this.trimAdtsFramesByEarliestDts_(adtsFrames);
- // we have to build the index from byte locations to
- // samples (that is, adts frames) in the audio data
- track.samples = this.generateSampleTable_(frames);
- // concatenate the audio data to constuct the mdat
- mdat = mp4.mdat(this.concatenateFrameData_(frames));
- adtsFrames = [];
- calculateTrackBaseMediaDecodeTime(track);
- moof = mp4.moof(sequenceNumber, [track]);
- boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
- // bump the sequence number for next time
- sequenceNumber++;
- boxes.set(moof);
- boxes.set(mdat, moof.byteLength);
- clearDtsInfo(track);
- this.trigger('data', {track: track, boxes: boxes});
- this.trigger('done', 'AudioSegmentStream');
- };
- // If the audio segment extends before the earliest allowed dts
- // value, remove AAC frames until starts at or after the earliest
- // allowed DTS so that we don't end up with a negative baseMedia-
- // DecodeTime for the audio track
- this.trimAdtsFramesByEarliestDts_ = function(adtsFrames) {
- if (track.minSegmentDts >= earliestAllowedDts) {
- return adtsFrames;
- }
- // We will need to recalculate the earliest segment Dts
- track.minSegmentDts = Infinity;
- return adtsFrames.filter(function(currentFrame) {
- // If this is an allowed frame, keep it and record it's Dts
- if (currentFrame.dts >= earliestAllowedDts) {
- track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
- track.minSegmentPts = track.minSegmentDts;
- return true;
- }
- // Otherwise, discard it
- return false;
- });
- };
- // generate the track's raw mdat data from an array of frames
- this.generateSampleTable_ = function(frames) {
- var
- i,
- currentFrame,
- samples = [];
- for (i = 0; i < frames.length; i++) {
- currentFrame = frames[i];
- samples.push({
- size: currentFrame.data.byteLength,
- duration: 1024 // For AAC audio, all samples contain 1024 samples
- });
- }
- return samples;
- };
- // generate the track's sample table from an array of frames
- this.concatenateFrameData_ = function(frames) {
- var
- i,
- currentFrame,
- dataOffset = 0,
- data = new Uint8Array(sumFrameByteLengths(frames));
- for (i = 0; i < frames.length; i++) {
- currentFrame = frames[i];
- data.set(currentFrame.data, dataOffset);
- dataOffset += currentFrame.data.byteLength;
- }
- return data;
- };
- };
- AudioSegmentStream.prototype = new Stream();
- /**
- * Constructs a single-track, ISO BMFF media segment from H264 data
- * events. The output of this stream can be fed to a SourceBuffer
- * configured with a suitable initialization segment.
- * @param track {object} track metadata configuration
- */
- VideoSegmentStream = function(track) {
- var
- sequenceNumber = 0,
- nalUnits = [],
- config,
- pps;
- VideoSegmentStream.prototype.init.call(this);
- delete track.minPTS;
- this.gopCache_ = [];
- this.push = function(nalUnit) {
- collectDtsInfo(track, nalUnit);
- // record the track config
- if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
- config = nalUnit.config;
- track.sps = [nalUnit.data];
- VIDEO_PROPERTIES.forEach(function(prop) {
- track[prop] = config[prop];
- }, this);
- }
- if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' &&
- !pps) {
- pps = nalUnit.data;
- track.pps = [nalUnit.data];
- }
- // buffer video until flush() is called
- nalUnits.push(nalUnit);
- };
- this.flush = function() {
- var
- frames,
- gopForFusion,
- gops,
- moof,
- mdat,
- boxes;
- // Throw away nalUnits at the start of the byte stream until
- // we find the first AUD
- while (nalUnits.length) {
- if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
- break;
- }
- nalUnits.shift();
- }
- // Return early if no video data has been observed
- if (nalUnits.length === 0) {
- this.resetStream_();
- this.trigger('done', 'VideoSegmentStream');
- return;
- }
- // Organize the raw nal-units into arrays that represent
- // higher-level constructs such as frames and gops
- // (group-of-pictures)
- frames = this.groupNalsIntoFrames_(nalUnits);
- gops = this.groupFramesIntoGops_(frames);
- // If the first frame of this fragment is not a keyframe we have
- // a problem since MSE (on Chrome) requires a leading keyframe.
- //
- // We have two approaches to repairing this situation:
- // 1) GOP-FUSION:
- // This is where we keep track of the GOPS (group-of-pictures)
- // from previous fragments and attempt to find one that we can
- // prepend to the current fragment in order to create a valid
- // fragment.
- // 2) KEYFRAME-PULLING:
- // Here we search for the first keyframe in the fragment and
- // throw away all the frames between the start of the fragment
- // and that keyframe. We then extend the duration and pull the
- // PTS of the keyframe forward so that it covers the time range
- // of the frames that were disposed of.
- //
- // #1 is far prefereable over #2 which can cause "stuttering" but
- // requires more things to be just right.
- if (!gops[0][0].keyFrame) {
- // Search for a gop for fusion from our gopCache
- gopForFusion = this.getGopForFusion_(nalUnits[0], track);
- if (gopForFusion) {
- gops.unshift(gopForFusion);
- // Adjust Gops' metadata to account for the inclusion of the
- // new gop at the beginning
- gops.byteLength += gopForFusion.byteLength;
- gops.nalCount += gopForFusion.nalCount;
- gops.pts = gopForFusion.pts;
- gops.dts = gopForFusion.dts;
- gops.duration += gopForFusion.duration;
- } else {
- // If we didn't find a candidate gop fall back to keyrame-pulling
- gops = this.extendFirstKeyFrame_(gops);
- }
- }
- collectDtsInfo(track, gops);
- // First, we have to build the index from byte locations to
- // samples (that is, frames) in the video data
- track.samples = this.generateSampleTable_(gops);
- // Concatenate the video data and construct the mdat
- mdat = mp4.mdat(this.concatenateNalData_(gops));
- // save all the nals in the last GOP into the gop cache
- this.gopCache_.unshift({
- gop: gops.pop(),
- pps: track.pps,
- sps: track.sps
- });
- // Keep a maximum of 6 GOPs in the cache
- this.gopCache_.length = Math.min(6, this.gopCache_.length);
- // Clear nalUnits
- nalUnits = [];
- calculateTrackBaseMediaDecodeTime(track);
- this.trigger('timelineStartInfo', track.timelineStartInfo);
- moof = mp4.moof(sequenceNumber, [track]);
- // it would be great to allocate this array up front instead of
- // throwing away hundreds of media segment fragments
- boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
- // Bump the sequence number for next time
- sequenceNumber++;
- boxes.set(moof);
- boxes.set(mdat, moof.byteLength);
- this.trigger('data', {track: track, boxes: boxes});
- this.resetStream_();
- // Continue with the flush process now
- this.trigger('done', 'VideoSegmentStream');
- };
- this.resetStream_ = function() {
- clearDtsInfo(track);
- // reset config and pps because they may differ across segments
- // for instance, when we are rendition switching
- config = undefined;
- pps = undefined;
- };
- // Search for a candidate Gop for gop-fusion from the gop cache and
- // return it or return null if no good candidate was found
- this.getGopForFusion_ = function (nalUnit) {
- var
- halfSecond = 45000, // Half-a-second in a 90khz clock
- allowableOverlap = 10000, // About 3 frames @ 30fps
- nearestDistance = Infinity,
- dtsDistance,
- nearestGopObj,
- currentGop,
- currentGopObj,
- i;
- // Search for the GOP nearest to the beginning of this nal unit
- for (i = 0; i < this.gopCache_.length; i++) {
- currentGopObj = this.gopCache_[i];
- currentGop = currentGopObj.gop;
- // Reject Gops with different SPS or PPS
- if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) ||
- !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
- continue;
- }
- // Reject Gops that would require a negative baseMediaDecodeTime
- if (currentGop.dts < track.timelineStartInfo.dts) {
- continue;
- }
- // The distance between the end of the gop and the start of the nalUnit
- dtsDistance = (nalUnit.dts - currentGop.dts) - currentGop.duration;
- // Only consider GOPS that start before the nal unit and end within
- // a half-second of the nal unit
- if (dtsDistance >= -allowableOverlap &&
- dtsDistance <= halfSecond) {
- // Always use the closest GOP we found if there is more than
- // one candidate
- if (!nearestGopObj ||
- nearestDistance > dtsDistance) {
- nearestGopObj = currentGopObj;
- nearestDistance = dtsDistance;
- }
- }
- }
- if (nearestGopObj) {
- return nearestGopObj.gop;
- }
- return null;
- };
- this.extendFirstKeyFrame_ = function(gops) {
- var
- h, i,
- currentGop,
- newGops;
- if (!gops[0][0].keyFrame) {
- // Remove the first GOP
- currentGop = gops.shift();
- gops.byteLength -= currentGop.byteLength;
- gops.nalCount -= currentGop.nalCount;
- // Extend the first frame of what is now the
- // first gop to cover the time period of the
- // frames we just removed
- gops[0][0].dts = currentGop.dts;
- gops[0][0].pts = currentGop.pts;
- gops[0][0].duration += currentGop.duration;
- }
- return gops;
- };
- // Convert an array of nal units into an array of frames with each frame being
- // composed of the nal units that make up that frame
- // Also keep track of cummulative data about the frame from the nal units such
- // as the frame duration, starting pts, etc.
- this.groupNalsIntoFrames_ = function(nalUnits) {
- var
- i,
- currentNal,
- startPts,
- startDts,
- currentFrame = [],
- frames = [];
- currentFrame.byteLength = 0;
- for (i = 0; i < nalUnits.length; i++) {
- currentNal = nalUnits[i];
- // Split on 'aud'-type nal units
- if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
- // Since the very first nal unit is expected to be an AUD
- // only push to the frames array when currentFrame is not empty
- if (currentFrame.length) {
- currentFrame.duration = currentNal.dts - currentFrame.dts;
- frames.push(currentFrame);
- }
- currentFrame = [currentNal];
- currentFrame.byteLength = currentNal.data.byteLength;
- currentFrame.pts = currentNal.pts;
- currentFrame.dts = currentNal.dts;
- } else {
- // Specifically flag key frames for ease of use later
- if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
- currentFrame.keyFrame = true;
- }
- currentFrame.duration = currentNal.dts - currentFrame.dts;
- currentFrame.byteLength += currentNal.data.byteLength;
- currentFrame.push(currentNal);
- }
- }
- // For the last frame, use the duration of the previous frame if we
- // have nothing better to go on
- if (frames.length &&
- (!currentFrame.duration ||
- currentFrame.duration <= 0)) {
- currentFrame.duration = frames[frames.length - 1].duration;
- }
- // Push the final frame
- frames.push(currentFrame);
- return frames;
- };
- // Convert an array of frames into an array of Gop with each Gop being composed
- // of the frames that make up that Gop
- // Also keep track of cummulative data about the Gop from the frames such as the
- // Gop duration, starting pts, etc.
- this.groupFramesIntoGops_ = function(frames) {
- var
- i,
- currentFrame,
- currentGop = [],
- gops = [];
- // We must pre-set some of the values on the Gop since we
- // keep running totals of these values
- currentGop.byteLength = 0;
- currentGop.nalCount = 0;
- currentGop.duration = 0;
- currentGop.pts = frames[0].pts;
- currentGop.dts = frames[0].dts;
- // store some metadata about all the Gops
- gops.byteLength = 0;
- gops.nalCount = 0;
- gops.duration = 0;
- gops.pts = frames[0].pts;
- gops.dts = frames[0].dts;
- for (i = 0; i < frames.length; i++) {
- currentFrame = frames[i];
- if (currentFrame.keyFrame) {
- // Since the very first frame is expected to be an keyframe
- // only push to the gops array when currentGop is not empty
- if (currentGop.length) {
- gops.push(currentGop);
- gops.byteLength += currentGop.byteLength;
- gops.nalCount += currentGop.nalCount;
- gops.duration += currentGop.duration;
- }
- currentGop = [currentFrame];
- currentGop.nalCount = currentFrame.length;
- currentGop.byteLength = currentFrame.byteLength;
- currentGop.pts = currentFrame.pts;
- currentGop.dts = currentFrame.dts;
- currentGop.duration = currentFrame.duration;
- } else {
- currentGop.duration += currentFrame.duration;
- currentGop.nalCount += currentFrame.length;
- currentGop.byteLength += currentFrame.byteLength;
- currentGop.push(currentFrame);
- }
- }
- if (gops.length && currentGop.duration <= 0) {
- currentGop.duration = gops[gops.length - 1].duration;
- }
- gops.byteLength += currentGop.byteLength;
- gops.nalCount += currentGop.nalCount;
- gops.duration += currentGop.duration;
- // push the final Gop
- gops.push(currentGop);
- return gops;
- };
- // generate the track's sample table from an array of gops
- this.generateSampleTable_ = function(gops, baseDataOffset) {
- var
- h, i,
- sample,
- currentGop,
- currentFrame,
- currentSample,
- dataOffset = baseDataOffset || 0,
- samples = [];
- for (h = 0; h < gops.length; h++) {
- currentGop = gops[h];
- for (i = 0; i < currentGop.length; i++) {
- currentFrame = currentGop[i];
- sample = createDefaultSample();
- sample.dataOffset = dataOffset;
- sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts;
- sample.duration = currentFrame.duration;
- sample.size = 4 * currentFrame.length; // Space for nal unit size
- sample.size += currentFrame.byteLength;
- if (currentFrame.keyFrame) {
- sample.flags.dependsOn = 2;
- }
- dataOffset += sample.size;
- samples.push(sample);
- }
- }
- return samples;
- };
- // generate the track's raw mdat data from an array of gops
- this.concatenateNalData_ = function (gops) {
- var
- h, i, j,
- currentGop,
- currentFrame,
- currentNal,
- dataOffset = 0,
- nalsByteLength = gops.byteLength,
- numberOfNals = gops.nalCount,
- totalByteLength = nalsByteLength + 4 * numberOfNals,
- data = new Uint8Array(totalByteLength),
- view = new DataView(data.buffer);
- // For each Gop..
- for (h = 0; h < gops.length; h++) {
- currentGop = gops[h];
- // For each Frame..
- for (i = 0; i < currentGop.length; i++) {
- currentFrame = currentGop[i];
- // For each NAL..
- for (j = 0; j < currentFrame.length; j++) {
- currentNal = currentFrame[j];
- view.setUint32(dataOffset, currentNal.data.byteLength);
- dataOffset += 4;
- data.set(currentNal.data, dataOffset);
- dataOffset += currentNal.data.byteLength;
- }
- }
- }
- return data;
- };
- };
- VideoSegmentStream.prototype = new Stream();
- /**
- * Store information about the start and end of the track and the
- * duration for each frame/sample we process in order to calculate
- * the baseMediaDecodeTime
- */
- collectDtsInfo = function (track, data) {
- if (typeof data.pts === 'number') {
- if (track.timelineStartInfo.pts === undefined) {
- track.timelineStartInfo.pts = data.pts;
- }
- if (track.minSegmentPts === undefined) {
- track.minSegmentPts = data.pts;
- } else {
- track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
- }
- if (track.maxSegmentPts === undefined) {
- track.maxSegmentPts = data.pts;
- } else {
- track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
- }
- }
- if (typeof data.dts === 'number') {
- if (track.timelineStartInfo.dts === undefined) {
- track.timelineStartInfo.dts = data.dts;
- }
- if (track.minSegmentDts === undefined) {
- track.minSegmentDts = data.dts;
- } else {
- track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
- }
- if (track.maxSegmentDts === undefined) {
- track.maxSegmentDts = data.dts;
- } else {
- track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
- }
- }
- };
- /**
- * Clear values used to calculate the baseMediaDecodeTime between
- * tracks
- */
- clearDtsInfo = function (track) {
- delete track.minSegmentDts;
- delete track.maxSegmentDts;
- delete track.minSegmentPts;
- delete track.maxSegmentPts;
- };
- /**
- * Calculate the track's baseMediaDecodeTime based on the earliest
- * DTS the transmuxer has ever seen and the minimum DTS for the
- * current track
- */
- calculateTrackBaseMediaDecodeTime = function (track) {
- var
- oneSecondInPTS = 90000, // 90kHz clock
- scale,
- // Calculate the distance, in time, that this segment starts from the start
- // of the timeline (earliest time seen since the transmuxer initialized)
- timeSinceStartOfTimeline = track.minSegmentDts - track.timelineStartInfo.dts,
- // Calculate the first sample's effective compositionTimeOffset
- firstSampleCompositionOffset = track.minSegmentPts - track.minSegmentDts;
- // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
- // we want the start of the first segment to be placed
- track.baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime;
- // Add to that the distance this segment is from the very first
- track.baseMediaDecodeTime += timeSinceStartOfTimeline;
- // Subtract this segment's "compositionTimeOffset" so that the first frame of
- // this segment is displayed exactly at the `baseMediaDecodeTime` or at the
- // end of the previous segment
- track.baseMediaDecodeTime -= firstSampleCompositionOffset;
- // baseMediaDecodeTime must not become negative
- track.baseMediaDecodeTime = Math.max(0, track.baseMediaDecodeTime);
- if (track.type === 'audio') {
- // Audio has a different clock equal to the sampling_rate so we need to
- // scale the PTS values into the clock rate of the track
- scale = track.samplerate / oneSecondInPTS;
- track.baseMediaDecodeTime *= scale;
- track.baseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime);
- }
- };
- /**
- * A Stream that can combine multiple streams (ie. audio & video)
- * into a single output segment for MSE. Also supports audio-only
- * and video-only streams.
- */
- CoalesceStream = function(options, metadataStream) {
- // Number of Tracks per output segment
- // If greater than 1, we combine multiple
- // tracks into a single segment
- this.numberOfTracks = 0;
- this.metadataStream = metadataStream;
- if (typeof options.remux !== 'undefined') {
- this.remuxTracks = !!options.remux;
- } else {
- this.remuxTracks = true;
- }
- this.pendingTracks = [];
- this.videoTrack = null;
- this.pendingBoxes = [];
- this.pendingCaptions = [];
- this.pendingMetadata = [];
- this.pendingBytes = 0;
- this.emittedTracks = 0;
- CoalesceStream.prototype.init.call(this);
- // Take output from multiple
- this.push = function(output) {
- // buffer incoming captions until the associated video segment
- // finishes
- if (output.text) {
- return this.pendingCaptions.push(output);
- }
- // buffer incoming id3 tags until the final flush
- if (output.frames) {
- return this.pendingMetadata.push(output);
- }
- // Add this track to the list of pending tracks and store
- // important information required for the construction of
- // the final segment
- this.pendingTracks.push(output.track);
- this.pendingBoxes.push(output.boxes);
- this.pendingBytes += output.boxes.byteLength;
- if (output.track.type === 'video') {
- this.videoTrack = output.track;
- }
- if (output.track.type === 'audio') {
- this.audioTrack = output.track;
- }
- };
- };
- CoalesceStream.prototype = new Stream();
- CoalesceStream.prototype.flush = function(flushSource) {
- var
- offset = 0,
- event = {
- captions: [],
- metadata: [],
- info: {}
- },
- caption,
- id3,
- initSegment,
- timelineStartPts = 0,
- i;
- if (this.pendingTracks.length < this.numberOfTracks) {
- if (flushSource !== 'VideoSegmentStream' &&
- flushSource !== 'AudioSegmentStream') {
- // Return because we haven't received a flush from a data-generating
- // portion of the segment (meaning that we have only recieved meta-data
- // or captions.)
- return;
- } else if (this.remuxTracks) {
- // Return until we have enough tracks from the pipeline to remux (if we
- // are remuxing audio and video into a single MP4)
- return;
- } else if (this.pendingTracks.length === 0) {
- // In the case where we receive a flush without any data having been
- // received we consider it an emitted track for the purposes of coalescing
- // `done` events.
- // We do this for the case where there is an audio and video track in the
- // segment but no audio data. (seen in several playlists with alternate
- // audio tracks and no audio present in the main TS segments.)
- this.emittedTracks++;
- if (this.emittedTracks >= this.numberOfTracks) {
- this.trigger('done');
- this.emittedTracks = 0;
- }
- return;
- }
- }
- if (this.videoTrack) {
- timelineStartPts = this.videoTrack.timelineStartInfo.pts;
- VIDEO_PROPERTIES.forEach(function(prop) {
- event.info[prop] = this.videoTrack[prop];
- }, this);
- } else if (this.audioTrack) {
- timelineStartPts = this.audioTrack.timelineStartInfo.pts;
- AUDIO_PROPERTIES.forEach(function(prop) {
- event.info[prop] = this.audioTrack[prop];
- }, this);
- }
- if (this.pendingTracks.length === 1) {
- event.type = this.pendingTracks[0].type;
- } else {
- event.type = 'combined';
- }
- this.emittedTracks += this.pendingTracks.length;
- initSegment = mp4.initSegment(this.pendingTracks);
- // Create a new typed array large enough to hold the init
- // segment and all tracks
- event.data = new Uint8Array(initSegment.byteLength +
- this.pendingBytes);
- // Create an init segment containing a moov
- // and track definitions
- event.data.set(initSegment);
- offset += initSegment.byteLength;
- // Append each moof+mdat (one per track) after the init segment
- for (i = 0; i < this.pendingBoxes.length; i++) {
- event.data.set(this.pendingBoxes[i], offset);
- offset += this.pendingBoxes[i].byteLength;
- }
- // Translate caption PTS times into second offsets into the
- // video timeline for the segment
- for (i = 0; i < this.pendingCaptions.length; i++) {
- caption = this.pendingCaptions[i];
- caption.startTime = (caption.startPts - timelineStartPts);
- caption.startTime /= 90e3;
- caption.endTime = (caption.endPts - timelineStartPts);
- caption.endTime /= 90e3;
- event.captions.push(caption);
- }
- // Translate ID3 frame PTS times into second offsets into the
- // video timeline for the segment
- for (i = 0; i < this.pendingMetadata.length; i++) {
- id3 = this.pendingMetadata[i];
- id3.cueTime = (id3.pts - timelineStartPts);
- id3.cueTime /= 90e3;
- event.metadata.push(id3);
- }
- // We add this to every single emitted segment even though we only need
- // it for the first
- event.metadata.dispatchType = this.metadataStream.dispatchType;
- // Reset stream state
- this.pendingTracks.length = 0;
- this.videoTrack = null;
- this.pendingBoxes.length = 0;
- this.pendingCaptions.length = 0;
- this.pendingBytes = 0;
- this.pendingMetadata.length = 0;
- // Emit the built segment
- this.trigger('data', event);
- // Only emit `done` if all tracks have been flushed and emitted
- if (this.emittedTracks >= this.numberOfTracks) {
- this.trigger('done');
- this.emittedTracks = 0;
- }
- };
- /**
- * A Stream that expects MP2T binary data as input and produces
- * corresponding media segments, suitable for use with Media Source
- * Extension (MSE) implementations that support the ISO BMFF byte
- * stream format, like Chrome.
- */
- Transmuxer = function(options) {
- var
- self = this,
- hasFlushed = true,
- videoTrack,
- audioTrack;
- Transmuxer.prototype.init.call(this);
- options = options || {};
- this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
- this.transmuxPipeline_ = {};
- this.setupAacPipeline = function() {
- var pipeline = {};
- this.transmuxPipeline_ = pipeline;
- pipeline.type = 'aac';
- pipeline.metadataStream = new m2ts.MetadataStream();
- // set up the parsing pipeline
- pipeline.aacStream = new AacStream();
- pipeline.adtsStream = new AdtsStream();
- pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
- pipeline.headOfPipeline = pipeline.aacStream;
- pipeline.aacStream.pipe(pipeline.adtsStream);
- pipeline.aacStream.pipe(pipeline.metadataStream);
- pipeline.metadataStream.pipe(pipeline.coalesceStream);
- pipeline.metadataStream.on('timestamp', function(frame) {
- pipeline.aacStream.setTimestamp(frame.timeStamp);
- });
- pipeline.aacStream.on('data', function(data) {
- var i;
- if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
- audioTrack = audioTrack || {
- timelineStartInfo: {
- baseMediaDecodeTime: self.baseMediaDecodeTime
- },
- codec: 'adts',
- type: 'audio'
- };
- // hook up the audio segment stream to the first track with aac data
- pipeline.coalesceStream.numberOfTracks++;
- pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack);
- // Set up the final part of the audio pipeline
- pipeline.adtsStream
- .pipe(pipeline.audioSegmentStream)
- .pipe(pipeline.coalesceStream);
- }
- });
- // Re-emit any data coming from the coalesce stream to the outside world
- pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
- // Let the consumer know we have finished flushing the entire pipeline
- pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
- };
- this.setupTsPipeline = function() {
- var pipeline = {};
- this.transmuxPipeline_ = pipeline;
- pipeline.type = 'ts';
- pipeline.metadataStream = new m2ts.MetadataStream();
- // set up the parsing pipeline
- pipeline.packetStream = new m2ts.TransportPacketStream();
- pipeline.parseStream = new m2ts.TransportParseStream();
- pipeline.elementaryStream = new m2ts.ElementaryStream();
- pipeline.adtsStream = new AdtsStream();
- pipeline.h264Stream = new H264Stream();
- pipeline.captionStream = new m2ts.CaptionStream();
- pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
- pipeline.headOfPipeline = pipeline.packetStream;
- // disassemble MPEG2-TS packets into elementary streams
- pipeline.packetStream
- .pipe(pipeline.parseStream)
- .pipe(pipeline.elementaryStream);
- // !!THIS ORDER IS IMPORTANT!!
- // demux the streams
- pipeline.elementaryStream
- .pipe(pipeline.h264Stream);
- pipeline.elementaryStream
- .pipe(pipeline.adtsStream);
- pipeline.elementaryStream
- .pipe(pipeline.metadataStream)
- .pipe(pipeline.coalesceStream);
- // Hook up CEA-608/708 caption stream
- pipeline.h264Stream.pipe(pipeline.captionStream)
- .pipe(pipeline.coalesceStream);
- pipeline.elementaryStream.on('data', function(data) {
- var i;
- if (data.type === 'metadata') {
- i = data.tracks.length;
- // scan the tracks listed in the metadata
- while (i--) {
- if (!videoTrack && data.tracks[i].type === 'video') {
- videoTrack = data.tracks[i];
- videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
- } else if (!audioTrack && data.tracks[i].type === 'audio') {
- audioTrack = data.tracks[i];
- audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
- }
- }
- // hook up the video segment stream to the first track with h264 data
- if (videoTrack && !pipeline.videoSegmentStream) {
- pipeline.coalesceStream.numberOfTracks++;
- pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack);
- pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo){
- // When video emits timelineStartInfo data after a flush, we forward that
- // info to the AudioSegmentStream, if it exists, because video timeline
- // data takes precedence.
- if (audioTrack) {
- audioTrack.timelineStartInfo = timelineStartInfo;
- // On the first segment we trim AAC frames that exist before the
- // very earliest DTS we have seen in video because Chrome will
- // interpret any video track with a baseMediaDecodeTime that is
- // non-zero as a gap.
- pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
- }
- });
- // Set up the final part of the video pipeline
- pipeline.h264Stream
- .pipe(pipeline.videoSegmentStream)
- .pipe(pipeline.coalesceStream);
- }
- if (audioTrack && !pipeline.audioSegmentStream) {
- // hook up the audio segment stream to the first track with aac data
- pipeline.coalesceStream.numberOfTracks++;
- pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack);
- // Set up the final part of the audio pipeline
- pipeline.adtsStream
- .pipe(pipeline.audioSegmentStream)
- .pipe(pipeline.coalesceStream);
- }
- }
- });
- // Re-emit any data coming from the coalesce stream to the outside world
- pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
- // Let the consumer know we have finished flushing the entire pipeline
- pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
- };
- // hook up the segment streams once track metadata is delivered
- this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
- var pipeline = this.transmuxPipeline_;
- this.baseMediaDecodeTime = baseMediaDecodeTime;
- if (audioTrack) {
- audioTrack.timelineStartInfo.dts = undefined;
- audioTrack.timelineStartInfo.pts = undefined;
- clearDtsInfo(audioTrack);
- audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
- }
- if (videoTrack) {
- if (pipeline.videoSegmentStream) {
- pipeline.videoSegmentStream.gopCache_ = [];
- }
- videoTrack.timelineStartInfo.dts = undefined;
- videoTrack.timelineStartInfo.pts = undefined;
- clearDtsInfo(videoTrack);
- videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
- }
- };
- // feed incoming data to the front of the parsing pipeline
- this.push = function(data) {
- if (hasFlushed) {
- var isAac = isLikelyAacData(data);
- if (isAac && this.transmuxPipeline_.type !== 'aac') {
- this.setupAacPipeline();
- } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
- this.setupTsPipeline();
- }
- hasFlushed = false;
- }
- this.transmuxPipeline_.headOfPipeline.push(data);
- };
- // flush any buffered data
- this.flush = function() {
- hasFlushed = true;
- // Start at the top of the pipeline and flush all pending work
- this.transmuxPipeline_.headOfPipeline.flush();
- };
- };
- Transmuxer.prototype = new Stream();
- module.exports = {
- Transmuxer: Transmuxer,
- VideoSegmentStream: VideoSegmentStream,
- AudioSegmentStream: AudioSegmentStream,
- AUDIO_PROPERTIES: AUDIO_PROPERTIES,
- VIDEO_PROPERTIES: VIDEO_PROPERTIES
- };
- },{"../aac":36,"../codecs/adts.js":37,"../codecs/h264":38,"../m2ts/m2ts.js":46,"../utils/stream.js":55,"./mp4-generator.js":50}],52:[function(require,module,exports){
- 'use strict';
- var
- tagTypes = {
- 0x08: 'audio',
- 0x09: 'video',
- 0x12: 'metadata'
- },
- hex = function (val) {
- return '0x' + ('00' + val.toString(16)).slice(-2).toUpperCase();
- },
- hexStringList = function (data) {
- var arr = [], i;
- /* jshint -W086 */
- while(data.byteLength > 0) {
- i = 0;
- switch(data.byteLength) {
- default:
- arr.push(hex(data[i++]));
- case 7:
- arr.push(hex(data[i++]));
- case 6:
- arr.push(hex(data[i++]));
- case 5:
- arr.push(hex(data[i++]));
- case 4:
- arr.push(hex(data[i++]));
- case 3:
- arr.push(hex(data[i++]));
- case 2:
- arr.push(hex(data[i++]));
- case 1:
- arr.push(hex(data[i++]));
- }
- data = data.subarray(i);
- }
- /* jshint +W086 */
- return arr.join(' ');
- },
- parseAVCTag = function (tag, obj) {
- var
- avcPacketTypes = [
- 'AVC Sequence Header',
- 'AVC NALU',
- 'AVC End-of-Sequence'
- ],
- nalUnitTypes = [
- 'unspecified',
- 'slice_layer_without_partitioning',
- 'slice_data_partition_a_layer',
- 'slice_data_partition_b_layer',
- 'slice_data_partition_c_layer',
- 'slice_layer_without_partitioning_idr',
- 'sei',
- 'seq_parameter_set',
- 'pic_parameter_set',
- 'access_unit_delimiter',
- 'end_of_seq',
- 'end_of_stream',
- 'filler',
- 'seq_parameter_set_ext',
- 'prefix_nal_unit',
- 'subset_seq_parameter_set',
- 'reserved',
- 'reserved',
- 'reserved'
- ],
- compositionTime = (tag[1] & parseInt('01111111', 2) << 16) | (tag[2] << 8) | tag[3];
- obj = obj || {};
- obj.avcPacketType = avcPacketTypes[tag[0]];
- obj.CompositionTime = (tag[1] & parseInt('10000000', 2)) ? -compositionTime : compositionTime;
- if (tag[0] === 1) {
- obj.nalUnitTypeRaw = hexStringList(tag.subarray(4, 100));
- } else {
- obj.data = hexStringList(tag.subarray(4));
- }
- return obj;
- },
- parseVideoTag = function (tag, obj) {
- var
- frameTypes = [
- 'Unknown',
- 'Keyframe (for AVC, a seekable frame)',
- 'Inter frame (for AVC, a nonseekable frame)',
- 'Disposable inter frame (H.263 only)',
- 'Generated keyframe (reserved for server use only)',
- 'Video info/command frame'
- ],
- codecIDs = [
- 'JPEG (currently unused)',
- 'Sorenson H.263',
- 'Screen video',
- 'On2 VP6',
- 'On2 VP6 with alpha channel',
- 'Screen video version 2',
- 'AVC'
- ],
- codecID = tag[0] & parseInt('00001111', 2);
- obj = obj || {};
- obj.frameType = frameTypes[(tag[0] & parseInt('11110000', 2)) >>> 4];
- obj.codecID = codecID;
- if (codecID === 7) {
- return parseAVCTag(tag.subarray(1), obj);
- }
- return obj;
- },
- parseAACTag = function (tag, obj) {
- var packetTypes = [
- 'AAC Sequence Header',
- 'AAC Raw'
- ];
- obj = obj || {};
- obj.aacPacketType = packetTypes[tag[0]];
- obj.data = hexStringList(tag.subarray(1));
- return obj;
- },
- parseAudioTag = function (tag, obj) {
- var
- formatTable = [
- 'Linear PCM, platform endian',
- 'ADPCM',
- 'MP3',
- 'Linear PCM, little endian',
- 'Nellymoser 16-kHz mono',
- 'Nellymoser 8-kHz mono',
- 'Nellymoser',
- 'G.711 A-law logarithmic PCM',
- 'G.711 mu-law logarithmic PCM',
- 'reserved',
- 'AAC',
- 'Speex',
- 'MP3 8-Khz',
- 'Device-specific sound'
- ],
- samplingRateTable = [
- '5.5-kHz',
- '11-kHz',
- '22-kHz',
- '44-kHz'
- ],
- soundFormat = (tag[0] & parseInt('11110000', 2)) >>> 4;
- obj = obj || {};
- obj.soundFormat = formatTable[soundFormat];
- obj.soundRate = samplingRateTable[(tag[0] & parseInt('00001100', 2)) >>> 2];
- obj.soundSize = ((tag[0] & parseInt('00000010', 2)) >>> 1) ? '16-bit' : '8-bit';
- obj.soundType = (tag[0] & parseInt('00000001', 2)) ? 'Stereo' : 'Mono';
- if (soundFormat === 10) {
- return parseAACTag(tag.subarray(1), obj);
- }
- return obj;
- },
- parseGenericTag = function (tag) {
- return {
- tagType: tagTypes[tag[0]],
- dataSize: (tag[1] << 16) | (tag[2] << 8) | tag[3],
- timestamp: (tag[7] << 24) | (tag[4] << 16) | (tag[5] << 8) | tag[6],
- streamID: (tag[8] << 16) | (tag[9] << 8) | tag[10]
- };
- },
- inspectFlvTag = function (tag) {
- var header = parseGenericTag(tag);
- switch (tag[0]) {
- case 0x08:
- parseAudioTag(tag.subarray(11), header);
- break;
- case 0x09:
- parseVideoTag(tag.subarray(11), header);
- break;
- case 0x12:
- }
- return header;
- },
- inspectFlv = function (bytes) {
- var i = 9, // header
- dataSize,
- parsedResults = [],
- tag;
- // traverse the tags
- i += 4; // skip previous tag size
- while (i < bytes.byteLength) {
- dataSize = bytes[i + 1] << 16;
- dataSize |= bytes[i + 2] << 8;
- dataSize |= bytes[i + 3];
- dataSize += 11;
- tag = bytes.subarray(i, i + dataSize);
- parsedResults.push(inspectFlvTag(tag));
- i += dataSize + 4;
- }
- return parsedResults;
- },
- textifyFlv = function (flvTagArray) {
- return JSON.stringify(flvTagArray, null, 2);
- };
- module.exports = {
- inspectTag: inspectFlvTag,
- inspect: inspectFlv,
- textify: textifyFlv
- };
- },{}],53:[function(require,module,exports){
- (function (global){
- 'use strict';
- var
- inspectMp4,
- textifyMp4,
- /**
- * Returns the string representation of an ASCII encoded four byte buffer.
- * @param buffer {Uint8Array} a four-byte buffer to translate
- * @return {string} the corresponding string
- */
- parseType = function(buffer) {
- var result = '';
- result += String.fromCharCode(buffer[0]);
- result += String.fromCharCode(buffer[1]);
- result += String.fromCharCode(buffer[2]);
- result += String.fromCharCode(buffer[3]);
- return result;
- },
- parseMp4Date = function(seconds) {
- return new Date(seconds * 1000 - 2082844800000);
- },
- parseSampleFlags = function(flags) {
- return {
- isLeading: (flags[0] & 0x0c) >>> 2,
- dependsOn: flags[0] & 0x03,
- isDependedOn: (flags[1] & 0xc0) >>> 6,
- hasRedundancy: (flags[1] & 0x30) >>> 4,
- paddingValue: (flags[1] & 0x0e) >>> 1,
- isNonSyncSample: flags[1] & 0x01,
- degradationPriority: (flags[2] << 8) | flags[3]
- };
- },
- nalParse = function(avcStream) {
- var
- avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
- result = [],
- i,
- length;
- for (i = 0; i + 4 < avcStream.length; i += length) {
- length = avcView.getUint32(i);
- i += 4;
- // bail if this doesn't appear to be an H264 stream
- if (length <= 0) {
- result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
- continue;
- }
- switch(avcStream[i] & 0x1F) {
- case 0x01:
- result.push('slice_layer_without_partitioning_rbsp');
- break;
- case 0x05:
- result.push('slice_layer_without_partitioning_rbsp_idr');
- break;
- case 0x06:
- result.push('sei_rbsp');
- break;
- case 0x07:
- result.push('seq_parameter_set_rbsp');
- break;
- case 0x08:
- result.push('pic_parameter_set_rbsp');
- break;
- case 0x09:
- result.push('access_unit_delimiter_rbsp');
- break;
- default:
- result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
- break;
- }
- }
- return result;
- },
- // registry of handlers for individual mp4 box types
- parse = {
- // codingname, not a first-class box type. stsd entries share the
- // same format as real boxes so the parsing infrastructure can be
- // shared
- avc1: function(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- dataReferenceIndex: view.getUint16(6),
- width: view.getUint16(24),
- height: view.getUint16(26),
- horizresolution: view.getUint16(28) + (view.getUint16(30) / 16),
- vertresolution: view.getUint16(32) + (view.getUint16(34) / 16),
- frameCount: view.getUint16(40),
- depth: view.getUint16(74),
- config: inspectMp4(data.subarray(78, data.byteLength))
- };
- },
- avcC: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- configurationVersion: data[0],
- avcProfileIndication: data[1],
- profileCompatibility: data[2],
- avcLevelIndication: data[3],
- lengthSizeMinusOne: data[4] & 0x03,
- sps: [],
- pps: []
- },
- numOfSequenceParameterSets = data[5] & 0x1f,
- numOfPictureParameterSets,
- nalSize,
- offset,
- i;
- // iterate past any SPSs
- offset = 6;
- for (i = 0; i < numOfSequenceParameterSets; i++) {
- nalSize = view.getUint16(offset);
- offset += 2;
- result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
- offset += nalSize;
- }
- // iterate past any PPSs
- numOfPictureParameterSets = data[offset];
- offset++;
- for (i = 0; i < numOfPictureParameterSets; i++) {
- nalSize = view.getUint16(offset);
- offset += 2;
- result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
- offset += nalSize;
- }
- return result;
- },
- btrt: function(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- bufferSizeDB: view.getUint32(0),
- maxBitrate: view.getUint32(4),
- avgBitrate: view.getUint32(8)
- };
- },
- esds: function(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- esId: (data[6] << 8) | data[7],
- streamPriority: data[8] & 0x1f,
- decoderConfig: {
- objectProfileIndication: data[11],
- streamType: (data[12] >>> 2) & 0x3f,
- bufferSize: (data[13] << 16) | (data[14] << 8) | data[15],
- maxBitrate: (data[16] << 24) |
- (data[17] << 16) |
- (data[18] << 8) |
- data[19],
- avgBitrate: (data[20] << 24) |
- (data[21] << 16) |
- (data[22] << 8) |
- data[23],
- decoderConfigDescriptor: {
- tag: data[24],
- length: data[25],
- audioObjectType: (data[26] >>> 3) & 0x1f,
- samplingFrequencyIndex: ((data[26] & 0x07) << 1) |
- ((data[27] >>> 7) & 0x01),
- channelConfiguration: (data[27] >>> 3) & 0x0f
- }
- }
- };
- },
- ftyp: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- majorBrand: parseType(data.subarray(0, 4)),
- minorVersion: view.getUint32(4),
- compatibleBrands: []
- },
- i = 8;
- while (i < data.byteLength) {
- result.compatibleBrands.push(parseType(data.subarray(i, i + 4)));
- i += 4;
- }
- return result;
- },
- dinf: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- dref: function(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- dataReferences: inspectMp4(data.subarray(8))
- };
- },
- hdlr: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- language,
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- handlerType: parseType(data.subarray(8, 12)),
- name: ''
- },
- i = 8;
- // parse out the name field
- for (i = 24; i < data.byteLength; i++) {
- if (data[i] === 0x00) {
- // the name field is null-terminated
- i++;
- break;
- }
- result.name += String.fromCharCode(data[i]);
- }
- // decode UTF-8 to javascript's internal representation
- // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
- result.name = decodeURIComponent(global.escape(result.name));
- return result;
- },
- mdat: function(data) {
- return {
- byteLength: data.byteLength,
- nals: nalParse(data)
- };
- },
- mdhd: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- i = 4,
- language,
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- language: ''
- };
- if (result.version === 1) {
- i += 4;
- result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 8;
- result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 4;
- result.timescale = view.getUint32(i);
- i += 8;
- result.duration = view.getUint32(i); // truncating top 4 bytes
- } else {
- result.creationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.modificationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.timescale = view.getUint32(i);
- i += 4;
- result.duration = view.getUint32(i);
- }
- i += 4;
- // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
- // each field is the packed difference between its ASCII value and 0x60
- language = view.getUint16(i);
- result.language += String.fromCharCode((language >> 10) + 0x60);
- result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60);
- result.language += String.fromCharCode((language & 0x1f) + 0x60);
- return result;
- },
- mdia: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- mfhd: function(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- sequenceNumber: (data[4] << 24) |
- (data[5] << 16) |
- (data[6] << 8) |
- (data[7])
- };
- },
- minf: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- // codingname, not a first-class box type. stsd entries share the
- // same format as real boxes so the parsing infrastructure can be
- // shared
- mp4a: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- // 6 bytes reserved
- dataReferenceIndex: view.getUint16(6),
- // 4 + 4 bytes reserved
- channelcount: view.getUint16(16),
- samplesize: view.getUint16(18),
- // 2 bytes pre_defined
- // 2 bytes reserved
- samplerate: view.getUint16(24) + (view.getUint16(26) / 65536)
- };
- // if there are more bytes to process, assume this is an ISO/IEC
- // 14496-14 MP4AudioSampleEntry and parse the ESDBox
- if (data.byteLength > 28) {
- result.streamDescriptor = inspectMp4(data.subarray(28))[0];
- }
- return result;
- },
- moof: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- moov: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- mvex: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- mvhd: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- i = 4,
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4))
- };
- if (result.version === 1) {
- i += 4;
- result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 8;
- result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 4;
- result.timescale = view.getUint32(i);
- i += 8;
- result.duration = view.getUint32(i); // truncating top 4 bytes
- } else {
- result.creationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.modificationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.timescale = view.getUint32(i);
- i += 4;
- result.duration = view.getUint32(i);
- }
- i += 4;
- // convert fixed-point, base 16 back to a number
- result.rate = view.getUint16(i) + (view.getUint16(i + 2) / 16);
- i += 4;
- result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
- i += 2;
- i += 2;
- i += 2 * 4;
- result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
- i += 9 * 4;
- i += 6 * 4;
- result.nextTrackId = view.getUint32(i);
- return result;
- },
- pdin: function(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- rate: view.getUint32(4),
- initialDelay: view.getUint32(8)
- };
- },
- sdtp: function(data) {
- var
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- samples: []
- }, i;
- for (i = 4; i < data.byteLength; i++) {
- result.samples.push({
- dependsOn: (data[i] & 0x30) >> 4,
- isDependedOn: (data[i] & 0x0c) >> 2,
- hasRedundancy: data[i] & 0x03
- });
- }
- return result;
- },
- sidx: function(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- references: [],
- referenceId: view.getUint32(4),
- timescale: view.getUint32(8),
- earliestPresentationTime: view.getUint32(12),
- firstOffset: view.getUint32(16)
- },
- referenceCount = view.getUint16(22),
- i;
- for (i = 24; referenceCount; i += 12, referenceCount-- ) {
- result.references.push({
- referenceType: (data[i] & 0x80) >>> 7,
- referencedSize: view.getUint32(i) & 0x7FFFFFFF,
- subsegmentDuration: view.getUint32(i + 4),
- startsWithSap: !!(data[i + 8] & 0x80),
- sapType: (data[i + 8] & 0x70) >>> 4,
- sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
- });
- }
- return result;
- },
- smhd: function(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- balance: data[4] + (data[5] / 256)
- };
- },
- stbl: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- stco: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- chunkOffsets: []
- },
- entryCount = view.getUint32(4),
- i;
- for (i = 8; entryCount; i += 4, entryCount--) {
- result.chunkOffsets.push(view.getUint32(i));
- }
- return result;
- },
- stsc: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- entryCount = view.getUint32(4),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- sampleToChunks: []
- },
- i;
- for (i = 8; entryCount; i += 12, entryCount--) {
- result.sampleToChunks.push({
- firstChunk: view.getUint32(i),
- samplesPerChunk: view.getUint32(i + 4),
- sampleDescriptionIndex: view.getUint32(i + 8)
- });
- }
- return result;
- },
- stsd: function(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- sampleDescriptions: inspectMp4(data.subarray(8))
- };
- },
- stsz: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- sampleSize: view.getUint32(4),
- entries: []
- },
- i;
- for (i = 12; i < data.byteLength; i += 4) {
- result.entries.push(view.getUint32(i));
- }
- return result;
- },
- stts: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- timeToSamples: []
- },
- entryCount = view.getUint32(4),
- i;
- for (i = 8; entryCount; i += 8, entryCount--) {
- result.timeToSamples.push({
- sampleCount: view.getUint32(i),
- sampleDelta: view.getUint32(i + 4)
- });
- }
- return result;
- },
- styp: function(data) {
- return parse.ftyp(data);
- },
- tfdt: function(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
- };
- },
- tfhd: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- trackId: view.getUint32(4)
- },
- baseDataOffsetPresent = result.flags[2] & 0x01,
- sampleDescriptionIndexPresent = result.flags[2] & 0x02,
- defaultSampleDurationPresent = result.flags[2] & 0x08,
- defaultSampleSizePresent = result.flags[2] & 0x10,
- defaultSampleFlagsPresent = result.flags[2] & 0x20,
- i;
- i = 8;
- if (baseDataOffsetPresent) {
- i += 4; // truncate top 4 bytes
- result.baseDataOffset = view.getUint32(12);
- i += 4;
- }
- if (sampleDescriptionIndexPresent) {
- result.sampleDescriptionIndex = view.getUint32(i);
- i += 4;
- }
- if (defaultSampleDurationPresent) {
- result.defaultSampleDuration = view.getUint32(i);
- i += 4;
- }
- if (defaultSampleSizePresent) {
- result.defaultSampleSize = view.getUint32(i);
- i += 4;
- }
- if (defaultSampleFlagsPresent) {
- result.defaultSampleFlags = view.getUint32(i);
- }
- return result;
- },
- tkhd: function(data) {
- var
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- i = 4,
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- };
- if (result.version === 1) {
- i += 4;
- result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 8;
- result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 4;
- result.trackId = view.getUint32(i);
- i += 4;
- i += 8;
- result.duration = view.getUint32(i); // truncating top 4 bytes
- } else {
- result.creationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.modificationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.trackId = view.getUint32(i);
- i += 4;
- i += 4;
- result.duration = view.getUint32(i);
- }
- i += 4;
- i += 2 * 4;
- result.layer = view.getUint16(i);
- i += 2;
- result.alternateGroup = view.getUint16(i);
- i += 2;
- // convert fixed-point, base 16 back to a number
- result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
- i += 2;
- i += 2;
- result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
- i += 9 * 4;
- result.width = view.getUint16(i) + (view.getUint16(i + 2) / 16);
- i += 4;
- result.height = view.getUint16(i) + (view.getUint16(i + 2) / 16);
- return result;
- },
- traf: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- trak: function(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- trex: function(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- trackId: view.getUint32(4),
- defaultSampleDescriptionIndex: view.getUint32(8),
- defaultSampleDuration: view.getUint32(12),
- defaultSampleSize: view.getUint32(16),
- sampleDependsOn: data[20] & 0x03,
- sampleIsDependedOn: (data[21] & 0xc0) >> 6,
- sampleHasRedundancy: (data[21] & 0x30) >> 4,
- samplePaddingValue: (data[21] & 0x0e) >> 1,
- sampleIsDifferenceSample: !!(data[21] & 0x01),
- sampleDegradationPriority: view.getUint16(22)
- };
- },
- trun: function(data) {
- var
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- samples: []
- },
- view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- dataOffsetPresent = result.flags[2] & 0x01,
- firstSampleFlagsPresent = result.flags[2] & 0x04,
- sampleDurationPresent = result.flags[1] & 0x01,
- sampleSizePresent = result.flags[1] & 0x02,
- sampleFlagsPresent = result.flags[1] & 0x04,
- sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
- sampleCount = view.getUint32(4),
- offset = 8,
- sample;
- if (dataOffsetPresent) {
- result.dataOffset = view.getUint32(offset);
- offset += 4;
- }
- if (firstSampleFlagsPresent && sampleCount) {
- sample = {
- flags: parseSampleFlags(data.subarray(offset, offset + 4))
- };
- offset += 4;
- if (sampleDurationPresent) {
- sample.duration = view.getUint32(offset);
- offset += 4;
- }
- if (sampleSizePresent) {
- sample.size = view.getUint32(offset);
- offset += 4;
- }
- if (sampleCompositionTimeOffsetPresent) {
- sample.compositionTimeOffset = view.getUint32(offset);
- offset += 4;
- }
- result.samples.push(sample);
- sampleCount--;
- }
- while (sampleCount--) {
- sample = {};
- if (sampleDurationPresent) {
- sample.duration = view.getUint32(offset);
- offset += 4;
- }
- if (sampleSizePresent) {
- sample.size = view.getUint32(offset);
- offset += 4;
- }
- if (sampleFlagsPresent) {
- sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
- offset += 4;
- }
- if (sampleCompositionTimeOffsetPresent) {
- sample.compositionTimeOffset = view.getUint32(offset);
- offset += 4;
- }
- result.samples.push(sample);
- }
- return result;
- },
- 'url ': function(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4))
- };
- },
- vmhd: function(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- graphicsmode: view.getUint16(4),
- opcolor: new Uint16Array([view.getUint16(6),
- view.getUint16(8),
- view.getUint16(10)])
- };
- }
- };
- /**
- * Return a javascript array of box objects parsed from an ISO base
- * media file.
- * @param data {Uint8Array} the binary data of the media to be inspected
- * @return {array} a javascript array of potentially nested box objects
- */
- inspectMp4 = function(data) {
- var
- i = 0,
- result = [],
- view,
- size,
- type,
- end,
- box;
- // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
- var ab = new ArrayBuffer(data.length);
- var v = new Uint8Array(ab);
- for (var z = 0; z < data.length; ++z) {
- v[z] = data[z];
- }
- view = new DataView(ab);
- while (i < data.byteLength) {
- // parse box data
- size = view.getUint32(i);
- type = parseType(data.subarray(i + 4, i + 8));
- end = size > 1 ? i + size : data.byteLength;
- // parse type-specific data
- box = (parse[type] || function(data) {
- return {
- data: data
- };
- })(data.subarray(i + 8, end));
- box.size = size;
- box.type = type;
- // store this box and move to the next
- result.push(box);
- i = end;
- }
- return result;
- };
- /**
- * Returns a textual representation of the javascript represtentation
- * of an MP4 file. You can use it as an alternative to
- * JSON.stringify() to compare inspected MP4s.
- * @param inspectedMp4 {array} the parsed array of boxes in an MP4
- * file
- * @param depth {number} (optional) the number of ancestor boxes of
- * the elements of inspectedMp4. Assumed to be zero if unspecified.
- * @return {string} a text representation of the parsed MP4
- */
- textifyMp4 = function(inspectedMp4, depth) {
- var indent;
- depth = depth || 0;
- indent = new Array(depth * 2 + 1).join(' ');
- // iterate over all the boxes
- return inspectedMp4.map(function(box, index) {
- // list the box type first at the current indentation level
- return indent + box.type + '\n' +
- // the type is already included and handle child boxes separately
- Object.keys(box).filter(function(key) {
- return key !== 'type' && key !== 'boxes';
- // output all the box properties
- }).map(function(key) {
- var prefix = indent + ' ' + key + ': ',
- value = box[key];
- // print out raw bytes as hexademical
- if (value instanceof Uint8Array || value instanceof Uint32Array) {
- var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
- .map(function(byte) {
- return ' ' + ('00' + byte.toString(16)).slice(-2);
- }).join('').match(/.{1,24}/g);
- if (!bytes) {
- return prefix + '<>';
- }
- if (bytes.length === 1) {
- return prefix + '<' + bytes.join('').slice(1) + '>';
- }
- return prefix + '<\n' + bytes.map(function(line) {
- return indent + ' ' + line;
- }).join('\n') + '\n' + indent + ' >';
- }
- // stringify generic objects
- return prefix +
- JSON.stringify(value, null, 2)
- .split('\n').map(function(line, index) {
- if (index === 0) {
- return line;
- }
- return indent + ' ' + line;
- }).join('\n');
- }).join('\n') +
- // recursively textify the child boxes
- (box.boxes ? '\n' + textifyMp4(box.boxes, depth + 1) : '');
- }).join('\n');
- };
- module.exports = {
- inspect: inspectMp4,
- textify: textifyMp4
- };
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{}],54:[function(require,module,exports){
- 'use strict';
- var ExpGolomb;
- /**
- * Parser for exponential Golomb codes, a variable-bitwidth number encoding
- * scheme used by h264.
- */
- ExpGolomb = function(workingData) {
- var
- // the number of bytes left to examine in workingData
- workingBytesAvailable = workingData.byteLength,
- // the current word being examined
- workingWord = 0, // :uint
- // the number of bits left to examine in the current word
- workingBitsAvailable = 0; // :uint;
- // ():uint
- this.length = function() {
- return (8 * workingBytesAvailable);
- };
- // ():uint
- this.bitsAvailable = function() {
- return (8 * workingBytesAvailable) + workingBitsAvailable;
- };
- // ():void
- this.loadWord = function() {
- var
- position = workingData.byteLength - workingBytesAvailable,
- workingBytes = new Uint8Array(4),
- availableBytes = Math.min(4, workingBytesAvailable);
- if (availableBytes === 0) {
- throw new Error('no bytes available');
- }
- workingBytes.set(workingData.subarray(position,
- position + availableBytes));
- workingWord = new DataView(workingBytes.buffer).getUint32(0);
- // track the amount of workingData that has been processed
- workingBitsAvailable = availableBytes * 8;
- workingBytesAvailable -= availableBytes;
- };
- // (count:int):void
- this.skipBits = function(count) {
- var skipBytes; // :int
- if (workingBitsAvailable > count) {
- workingWord <<= count;
- workingBitsAvailable -= count;
- } else {
- count -= workingBitsAvailable;
- skipBytes = Math.floor(count / 8);
- count -= (skipBytes * 8);
- workingBytesAvailable -= skipBytes;
- this.loadWord();
- workingWord <<= count;
- workingBitsAvailable -= count;
- }
- };
- // (size:int):uint
- this.readBits = function(size) {
- var
- bits = Math.min(workingBitsAvailable, size), // :uint
- valu = workingWord >>> (32 - bits); // :uint
- // if size > 31, handle error
- workingBitsAvailable -= bits;
- if (workingBitsAvailable > 0) {
- workingWord <<= bits;
- } else if (workingBytesAvailable > 0) {
- this.loadWord();
- }
- bits = size - bits;
- if (bits > 0) {
- return valu << bits | this.readBits(bits);
- } else {
- return valu;
- }
- };
- // ():uint
- this.skipLeadingZeros = function() {
- var leadingZeroCount; // :uint
- for (leadingZeroCount = 0 ; leadingZeroCount < workingBitsAvailable ; ++leadingZeroCount) {
- if (0 !== (workingWord & (0x80000000 >>> leadingZeroCount))) {
- // the first bit of working word is 1
- workingWord <<= leadingZeroCount;
- workingBitsAvailable -= leadingZeroCount;
- return leadingZeroCount;
- }
- }
- // we exhausted workingWord and still have not found a 1
- this.loadWord();
- return leadingZeroCount + this.skipLeadingZeros();
- };
- // ():void
- this.skipUnsignedExpGolomb = function() {
- this.skipBits(1 + this.skipLeadingZeros());
- };
- // ():void
- this.skipExpGolomb = function() {
- this.skipBits(1 + this.skipLeadingZeros());
- };
- // ():uint
- this.readUnsignedExpGolomb = function() {
- var clz = this.skipLeadingZeros(); // :uint
- return this.readBits(clz + 1) - 1;
- };
- // ():int
- this.readExpGolomb = function() {
- var valu = this.readUnsignedExpGolomb(); // :int
- if (0x01 & valu) {
- // the number is odd if the low order bit is set
- return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
- } else {
- return -1 * (valu >>> 1); // divide by two then make it negative
- }
- };
- // Some convenience functions
- // :Boolean
- this.readBoolean = function() {
- return 1 === this.readBits(1);
- };
- // ():int
- this.readUnsignedByte = function() {
- return this.readBits(8);
- };
- this.loadWord();
- };
- module.exports = ExpGolomb;
- },{}],55:[function(require,module,exports){
- /**
- * mux.js
- *
- * Copyright (c) 2014 Brightcove
- * All rights reserved.
- *
- * A lightweight readable stream implemention that handles event dispatching.
- * Objects that inherit from streams should call init in their constructors.
- */
- 'use strict';
- var Stream = function() {
- this.init = function() {
- var listeners = {};
- /**
- * Add a listener for a specified event type.
- * @param type {string} the event name
- * @param listener {function} the callback to be invoked when an event of
- * the specified type occurs
- */
- this.on = function(type, listener) {
- if (!listeners[type]) {
- listeners[type] = [];
- }
- listeners[type].push(listener);
- };
- /**
- * Remove a listener for a specified event type.
- * @param type {string} the event name
- * @param listener {function} a function previously registered for this
- * type of event through `on`
- */
- this.off = function(type, listener) {
- var index;
- if (!listeners[type]) {
- return false;
- }
- index = listeners[type].indexOf(listener);
- listeners[type].splice(index, 1);
- return index > -1;
- };
- /**
- * Trigger an event of the specified type on this stream. Any additional
- * arguments to this function are passed as parameters to event listeners.
- * @param type {string} the event name
- */
- this.trigger = function(type) {
- var callbacks, i, length, args;
- callbacks = listeners[type];
- if (!callbacks) {
- return;
- }
- // Slicing the arguments on every invocation of this method
- // can add a significant amount of overhead. Avoid the
- // intermediate object creation for the common case of a
- // single callback argument
- if (arguments.length === 2) {
- length = callbacks.length;
- for (i = 0; i < length; ++i) {
- callbacks[i].call(this, arguments[1]);
- }
- } else {
- args = [];
- i = arguments.length;
- for (i = 1; i < arguments.length; ++i) {
- args.push(arguments[i]);
- }
- length = callbacks.length;
- for (i = 0; i < length; ++i) {
- callbacks[i].apply(this, args);
- }
- }
- };
- /**
- * Destroys the stream and cleans up.
- */
- this.dispose = function() {
- listeners = {};
- };
- };
- };
- /**
- * Forwards all `data` events on this stream to the destination stream. The
- * destination stream should provide a method `push` to receive the data
- * events as they arrive.
- * @param destination {stream} the stream that will receive all `data` events
- * @param autoFlush {boolean} if false, we will not call `flush` on the destination
- * when the current stream emits a 'done' event
- * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
- */
- Stream.prototype.pipe = function(destination) {
- this.on('data', function(data) {
- destination.push(data);
- });
- this.on('done', function(flushSource) {
- destination.flush(flushSource);
- });
- return destination;
- };
- // Default stream functions that are expected to be overridden to perform
- // actual work. These are provided by the prototype as a sort of no-op
- // implementation so that we don't have to check for their existence in the
- // `pipe` function above.
- Stream.prototype.push = function(data) {
- this.trigger('data', data);
- };
- Stream.prototype.flush = function(flushSource) {
- this.trigger('done', flushSource);
- };
- module.exports = Stream;
- },{}],56:[function(require,module,exports){
- var bundleFn = arguments[3];
- var sources = arguments[4];
- var cache = arguments[5];
- var stringify = JSON.stringify;
- module.exports = function (fn) {
- var keys = [];
- var wkey;
- var cacheKeys = Object.keys(cache);
-
- for (var i = 0, l = cacheKeys.length; i < l; i++) {
- var key = cacheKeys[i];
- if (cache[key].exports === fn) {
- wkey = key;
- break;
- }
- }
-
- if (!wkey) {
- wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);
- var wcache = {};
- for (var i = 0, l = cacheKeys.length; i < l; i++) {
- var key = cacheKeys[i];
- wcache[key] = key;
- }
- sources[wkey] = [
- Function(['require','module','exports'], '(' + fn + ')(self)'),
- wcache
- ];
- }
- var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);
-
- var scache = {}; scache[wkey] = wkey;
- sources[skey] = [
- Function(['require'],'require(' + stringify(wkey) + ')(self)'),
- scache
- ];
-
- var src = '(' + bundleFn + ')({'
- + Object.keys(sources).map(function (key) {
- return stringify(key) + ':['
- + sources[key][0]
- + ',' + stringify(sources[key][1]) + ']'
- ;
- }).join(',')
- + '},{},[' + stringify(skey) + '])'
- ;
-
- var URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
-
- return new Worker(URL.createObjectURL(
- new Blob([src], { type: 'text/javascript' })
- ));
- };
- },{}],57:[function(require,module,exports){
- (function (global){
- /**
- * @file videojs-contrib-hls.js
- *
- * The main file for the HLS project.
- * License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE
- */
- 'use strict';
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
- var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
- function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
- var _globalDocument = require('global/document');
- var _globalDocument2 = _interopRequireDefault(_globalDocument);
- var _playlistLoader = require('./playlist-loader');
- var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
- var _playlist = require('./playlist');
- var _playlist2 = _interopRequireDefault(_playlist);
- var _xhr = require('./xhr');
- var _xhr2 = _interopRequireDefault(_xhr);
- var _decrypter = require('./decrypter');
- var _binUtils = require('./bin-utils');
- var _binUtils2 = _interopRequireDefault(_binUtils);
- var _videojsContribMediaSources = require('videojs-contrib-media-sources');
- var _m3u8 = require('./m3u8');
- var _m3u82 = _interopRequireDefault(_m3u8);
- var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
- var _videoJs2 = _interopRequireDefault(_videoJs);
- var _masterPlaylistController = require('./master-playlist-controller');
- var _masterPlaylistController2 = _interopRequireDefault(_masterPlaylistController);
- /**
- * determine if an object a is differnt from
- * and object b. both only having one dimensional
- * properties
- *
- * @param {Object} a object one
- * @param {Object} b object two
- * @return {Boolean} if the object has changed or not
- */
- var objectChanged = function objectChanged(a, b) {
- if (typeof a !== typeof b) {
- return true;
- }
- // if we have a different number of elements
- // something has changed
- if (Object.keys(a).length !== Object.keys(b).length) {
- return true;
- }
- for (var prop in a) {
- if (!b[prop] || a[prop] !== b[prop]) {
- return true;
- }
- }
- return false;
- };
- var Hls = {
- PlaylistLoader: _playlistLoader2['default'],
- Playlist: _playlist2['default'],
- Decrypter: _decrypter.Decrypter,
- AsyncStream: _decrypter.AsyncStream,
- decrypt: _decrypter.decrypt,
- utils: _binUtils2['default'],
- xhr: (0, _xhr2['default'])()
- };
- // the desired length of video to maintain in the buffer, in seconds
- Hls.GOAL_BUFFER_LENGTH = 30;
- // A fudge factor to apply to advertised playlist bitrates to account for
- // temporary flucations in client bandwidth
- var BANDWIDTH_VARIANCE = 1.2;
- /**
- * Returns the CSS value for the specified property on an element
- * using `getComputedStyle`. Firefox has a long-standing issue where
- * getComputedStyle() may return null when running in an iframe with
- * `display: none`.
- *
- * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
- * @param {HTMLElement} el the htmlelement to work on
- * @param {string} the proprety to get the style for
- */
- var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
- var result = undefined;
- if (!el) {
- return '';
- }
- result = getComputedStyle(el);
- if (!result) {
- return '';
- }
- return result[property];
- };
- /**
- * Chooses the appropriate media playlist based on the current
- * bandwidth estimate and the player size.
- *
- * @return {Playlist} the highest bitrate playlist less than the currently detected
- * bandwidth, accounting for some amount of bandwidth variance
- */
- Hls.STANDARD_PLAYLIST_SELECTOR = function () {
- var effectiveBitrate = undefined;
- var sortedPlaylists = this.playlists.master.playlists.slice();
- var bandwidthPlaylists = [];
- var now = +new Date();
- var i = undefined;
- var variant = undefined;
- var bandwidthBestVariant = undefined;
- var resolutionPlusOne = undefined;
- var resolutionPlusOneAttribute = undefined;
- var resolutionBestVariant = undefined;
- var width = undefined;
- var height = undefined;
- sortedPlaylists.sort(Hls.comparePlaylistBandwidth);
- // filter out any playlists that have been excluded due to
- // incompatible configurations or playback errors
- sortedPlaylists = sortedPlaylists.filter(function (localVariant) {
- if (typeof localVariant.excludeUntil !== 'undefined') {
- return now >= localVariant.excludeUntil;
- }
- return true;
- });
- // filter out any variant that has greater effective bitrate
- // than the current estimated bandwidth
- i = sortedPlaylists.length;
- while (i--) {
- variant = sortedPlaylists[i];
- // ignore playlists without bandwidth information
- if (!variant.attributes || !variant.attributes.BANDWIDTH) {
- continue;
- }
- effectiveBitrate = variant.attributes.BANDWIDTH * BANDWIDTH_VARIANCE;
- if (effectiveBitrate < this.bandwidth) {
- bandwidthPlaylists.push(variant);
- // since the playlists are sorted in ascending order by
- // bandwidth, the first viable variant is the best
- if (!bandwidthBestVariant) {
- bandwidthBestVariant = variant;
- }
- }
- }
- i = bandwidthPlaylists.length;
- // sort variants by resolution
- bandwidthPlaylists.sort(Hls.comparePlaylistResolution);
- // forget our old variant from above,
- // or we might choose that in high-bandwidth scenarios
- // (this could be the lowest bitrate rendition as we go through all of them above)
- variant = null;
- width = parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10);
- height = parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10);
- // iterate through the bandwidth-filtered playlists and find
- // best rendition by player dimension
- while (i--) {
- variant = bandwidthPlaylists[i];
- // ignore playlists without resolution information
- if (!variant.attributes || !variant.attributes.RESOLUTION || !variant.attributes.RESOLUTION.width || !variant.attributes.RESOLUTION.height) {
- continue;
- }
- // since the playlists are sorted, the first variant that has
- // dimensions less than or equal to the player size is the best
- var variantResolution = variant.attributes.RESOLUTION;
- if (variantResolution.width === width && variantResolution.height === height) {
- // if we have the exact resolution as the player use it
- resolutionPlusOne = null;
- resolutionBestVariant = variant;
- break;
- } else if (variantResolution.width < width && variantResolution.height < height) {
- // if both dimensions are less than the player use the
- // previous (next-largest) variant
- break;
- } else if (!resolutionPlusOne || variantResolution.width < resolutionPlusOneAttribute.width && variantResolution.height < resolutionPlusOneAttribute.height) {
- // If we still haven't found a good match keep a
- // reference to the previous variant for the next loop
- // iteration
- // By only saving variants if they are smaller than the
- // previously saved variant, we ensure that we also pick
- // the highest bandwidth variant that is just-larger-than
- // the video player
- resolutionPlusOne = variant;
- resolutionPlusOneAttribute = resolutionPlusOne.attributes.RESOLUTION;
- }
- }
- // fallback chain of variants
- return resolutionPlusOne || resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0];
- };
- // HLS is a source handler, not a tech. Make sure attempts to use it
- // as one do not cause exceptions.
- Hls.canPlaySource = function () {
- return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
- };
- /**
- * Whether the browser has built-in HLS support.
- */
- Hls.supportsNativeHls = (function () {
- var video = _globalDocument2['default'].createElement('video');
- // native HLS is definitely not supported if HTML5 video isn't
- if (!_videoJs2['default'].getComponent('Html5').isSupported()) {
- return false;
- }
- // HLS manifests can go by many mime-types
- var canPlay = [
- // Apple santioned
- 'application/vnd.apple.mpegurl',
- // Apple sanctioned for backwards compatibility
- 'audio/mpegurl',
- // Very common
- 'audio/x-mpegurl',
- // Very common
- 'application/x-mpegurl',
- // Included for completeness
- 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
- return canPlay.some(function (canItPlay) {
- return (/maybe|probably/i.test(video.canPlayType(canItPlay))
- );
- });
- })();
- /**
- * HLS is a source handler, not a tech. Make sure attempts to use it
- * as one do not cause exceptions.
- */
- Hls.isSupported = function () {
- return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
- };
- var Component = _videoJs2['default'].getComponent('Component');
- /**
- * The Hls Handler object, where we orchestrate all of the parts
- * of HLS to interact with video.js
- *
- * @class HlsHandler
- * @extends videojs.Component
- * @param {Object} source the soruce object
- * @param {Tech} tech the parent tech object
- * @param {Object} options optional and required options
- */
- var HlsHandler = (function (_Component) {
- _inherits(HlsHandler, _Component);
- function HlsHandler(source, tech, options) {
- var _this = this;
- _classCallCheck(this, HlsHandler);
- _get(Object.getPrototypeOf(HlsHandler.prototype), 'constructor', this).call(this, tech);
- // tech.player() is deprecated but setup a reference to HLS for
- // backwards-compatibility
- if (tech.options_ && tech.options_.playerId) {
- var _player = (0, _videoJs2['default'])(tech.options_.playerId);
- if (!_player.hasOwnProperty('hls')) {
- Object.defineProperty(_player, 'hls', {
- get: function get() {
- _videoJs2['default'].log.warn('player.hls is deprecated. Use player.tech.hls instead.');
- return _this;
- }
- });
- }
- }
- this.options_ = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls || {}, options.hls);
- this.tech_ = tech;
- this.source_ = source;
- // start playlist selection at a reasonable bandwidth for
- // broadband internet
- // 0.5 Mbps
- this.bandwidth = this.options_.bandwidth || 4194304;
- this.bytesReceived = 0;
- // listen for fullscreenchange events for this player so that we
- // can adjust our quality selection quickly
- this.on(_globalDocument2['default'], ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
- var fullscreenElement = _globalDocument2['default'].fullscreenElement || _globalDocument2['default'].webkitFullscreenElement || _globalDocument2['default'].mozFullScreenElement || _globalDocument2['default'].msFullscreenElement;
- if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
- _this.masterPlaylistController_.fastQualityChange_();
- }
- });
- this.on(this.tech_, 'seeking', function () {
- this.setCurrentTime(this.tech_.currentTime());
- });
- this.on(this.tech_, 'error', function () {
- if (this.masterPlaylistController_) {
- this.masterPlaylistController_.pauseLoading();
- }
- });
- this.audioTrackChange_ = function () {
- _this.masterPlaylistController_.useAudio();
- };
- this.on(this.tech_, 'play', this.play);
- }
- /**
- * The Source Handler object, which informs video.js what additional
- * MIME types are supported and sets up playback. It is registered
- * automatically to the appropriate tech based on the capabilities of
- * the browser it is running in. It is not necessary to use or modify
- * this object in normal usage.
- */
- /**
- * called when player.src gets called, handle a new source
- *
- * @param {Object} src the source object to handle
- */
- _createClass(HlsHandler, [{
- key: 'src',
- value: function src(_src) {
- var _this2 = this;
- // do nothing if the src is falsey
- if (!_src) {
- return;
- }
- ['withCredentials', 'bandwidth'].forEach(function (option) {
- if (typeof _this2.source_[option] !== 'undefined') {
- _this2.options_[option] = _this2.source_[option];
- }
- });
- this.options_.url = this.source_.src;
- this.options_.tech = this.tech_;
- this.options_.externHls = Hls;
- this.options_.bandwidth = this.bandwidth;
- this.masterPlaylistController_ = new _masterPlaylistController2['default'](this.options_);
- // `this` in selectPlaylist should be the HlsHandler for backwards
- // compatibility with < v2
- this.masterPlaylistController_.selectPlaylist = Hls.STANDARD_PLAYLIST_SELECTOR.bind(this);
- // re-expose some internal objects for backwards compatibility with < v2
- this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
- this.mediaSource = this.masterPlaylistController_.mediaSource;
- // Proxy assignment of some properties to the master playlist
- // controller. Using a custom property for backwards compatibility
- // with < v2
- Object.defineProperties(this, {
- selectPlaylist: {
- get: function get() {
- return this.masterPlaylistController_.selectPlaylist;
- },
- set: function set(selectPlaylist) {
- this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
- }
- },
- bandwidth: {
- get: function get() {
- return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
- },
- set: function set(bandwidth) {
- this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth;
- }
- }
- });
- this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
- this.masterPlaylistController_.on('sourceopen', function () {
- _this2.tech_.audioTracks().addEventListener('change', _this2.audioTrackChange_);
- });
- this.masterPlaylistController_.on('audioinfo', function (e) {
- if (!_videoJs2['default'].browser.IS_FIREFOX || !_this2.audioInfo_ || !objectChanged(_this2.audioInfo_, e.info)) {
- _this2.audioInfo_ = e.info;
- return;
- }
- var error = 'had different audio properties (channels, sample rate, etc.) ' + 'or changed in some other way. This behavior is currently ' + 'unsupported in Firefox due to an issue: \n\n' + 'https://bugzilla.mozilla.org/show_bug.cgi?id=1247138\n\n';
- var enabledTrack = undefined;
- var defaultTrack = undefined;
- _this2.masterPlaylistController_.audioTracks_.forEach(function (t) {
- if (!defaultTrack && t['default']) {
- defaultTrack = t;
- }
- if (!enabledTrack && t.enabled) {
- enabledTrack = t;
- }
- });
- // they did not switch audiotracks
- // blacklist the current playlist
- if (!enabledTrack.getLoader(_this2.activeAudioGroup_())) {
- error = 'The rendition that we tried to switch to ' + error + 'Unfortunately that means we will have to blacklist ' + 'the current playlist and switch to another. Sorry!';
- _this2.masterPlaylistController_.blacklistCurrentPlaylist();
- } else {
- error = 'The audio track \'' + enabledTrack.label + '\' that we tried to ' + ('switch to ' + error + ' Unfortunately this means we will have to ') + ('return you to the main track \'' + defaultTrack.label + '\'. Sorry!');
- defaultTrack.enabled = true;
- _this2.tech_.audioTracks().removeTrack(enabledTrack);
- }
- _videoJs2['default'].log.warn(error);
- _this2.masterPlaylistController_.useAudio();
- });
- this.masterPlaylistController_.on('selectedinitialmedia', function () {
- // clear current audioTracks
- _this2.tech_.clearTracks('audio');
- _this2.masterPlaylistController_.audioTracks_.forEach(function (track) {
- _this2.tech_.audioTracks().addTrack(track);
- });
- });
- // the bandwidth of the primary segment loader is our best
- // estimate of overall bandwidth
- this.on(this.masterPlaylistController_, 'progress', function () {
- this.bandwidth = this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
- this.tech_.trigger('progress');
- });
- // do nothing if the tech has been disposed already
- // this can occur if someone sets the src in player.ready(), for instance
- if (!this.tech_.el()) {
- return;
- }
- this.tech_.src(_videoJs2['default'].URL.createObjectURL(this.masterPlaylistController_.mediaSource));
- }
- /**
- * a helper for grabbing the active audio group from MasterPlaylistController
- *
- * @private
- */
- }, {
- key: 'activeAudioGroup_',
- value: function activeAudioGroup_() {
- return this.masterPlaylistController_.activeAudioGroup();
- }
- /**
- * Begin playing the video.
- */
- }, {
- key: 'play',
- value: function play() {
- this.masterPlaylistController_.play();
- }
- /**
- * a wrapper around the function in MasterPlaylistController
- */
- }, {
- key: 'setCurrentTime',
- value: function setCurrentTime(currentTime) {
- this.masterPlaylistController_.setCurrentTime(currentTime);
- }
- /**
- * a wrapper around the function in MasterPlaylistController
- */
- }, {
- key: 'duration',
- value: function duration() {
- return this.masterPlaylistController_.duration();
- }
- /**
- * a wrapper around the function in MasterPlaylistController
- */
- }, {
- key: 'seekable',
- value: function seekable() {
- return this.masterPlaylistController_.seekable();
- }
- /**
- * Abort all outstanding work and cleanup.
- */
- }, {
- key: 'dispose',
- value: function dispose() {
- if (this.masterPlaylistController_) {
- this.masterPlaylistController_.dispose();
- }
- this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_);
- _get(Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
- }
- }]);
- return HlsHandler;
- })(Component);
- var HlsSourceHandler = function HlsSourceHandler(mode) {
- return {
- canHandleSource: function canHandleSource(srcObj) {
- // this forces video.js to skip this tech/mode if its not the one we have been
- // overriden to use, by returing that we cannot handle the source.
- if (_videoJs2['default'].options.hls && _videoJs2['default'].options.hls.mode && _videoJs2['default'].options.hls.mode !== mode) {
- return false;
- }
- return HlsSourceHandler.canPlayType(srcObj.type);
- },
- handleSource: function handleSource(source, tech, options) {
- if (mode === 'flash') {
- // We need to trigger this asynchronously to give others the chance
- // to bind to the event when a source is set at player creation
- tech.setTimeout(function () {
- tech.trigger('loadstart');
- }, 1);
- }
- var settings = _videoJs2['default'].mergeOptions(options, { hls: { mode: mode } });
- tech.hls = new HlsHandler(source, tech, settings);
- tech.hls.xhr = (0, _xhr2['default'])();
- // Use a global `before` function if specified on videojs.Hls.xhr
- // but still allow for a per-player override
- if (_videoJs2['default'].Hls.xhr.beforeRequest) {
- tech.hls.xhr.beforeRequest = _videoJs2['default'].Hls.xhr.beforeRequest;
- }
- tech.hls.src(source.src);
- return tech.hls;
- },
- canPlayType: function canPlayType(type) {
- if (HlsSourceHandler.canPlayType(type)) {
- return 'maybe';
- }
- return '';
- }
- };
- };
- /**
- * A comparator function to sort two playlist object by bandwidth.
- *
- * @param {Object} left a media playlist object
- * @param {Object} right a media playlist object
- * @return {Number} Greater than zero if the bandwidth attribute of
- * left is greater than the corresponding attribute of right. Less
- * than zero if the bandwidth of right is greater than left and
- * exactly zero if the two are equal.
- */
- Hls.comparePlaylistBandwidth = function (left, right) {
- var leftBandwidth = undefined;
- var rightBandwidth = undefined;
- if (left.attributes && left.attributes.BANDWIDTH) {
- leftBandwidth = left.attributes.BANDWIDTH;
- }
- leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
- if (right.attributes && right.attributes.BANDWIDTH) {
- rightBandwidth = right.attributes.BANDWIDTH;
- }
- rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
- return leftBandwidth - rightBandwidth;
- };
- /**
- * A comparator function to sort two playlist object by resolution (width).
- * @param {Object} left a media playlist object
- * @param {Object} right a media playlist object
- * @return {Number} Greater than zero if the resolution.width attribute of
- * left is greater than the corresponding attribute of right. Less
- * than zero if the resolution.width of right is greater than left and
- * exactly zero if the two are equal.
- */
- Hls.comparePlaylistResolution = function (left, right) {
- var leftWidth = undefined;
- var rightWidth = undefined;
- if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
- leftWidth = left.attributes.RESOLUTION.width;
- }
- leftWidth = leftWidth || window.Number.MAX_VALUE;
- if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
- rightWidth = right.attributes.RESOLUTION.width;
- }
- rightWidth = rightWidth || window.Number.MAX_VALUE;
- // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
- // have the same media dimensions/ resolution
- if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
- return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
- }
- return leftWidth - rightWidth;
- };
- HlsSourceHandler.canPlayType = function (type) {
- var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
- // favor native HLS support if it's available
- if (Hls.supportsNativeHls) {
- return false;
- }
- return mpegurlRE.test(type);
- };
- if (typeof _videoJs2['default'].MediaSource === 'undefined' || typeof _videoJs2['default'].URL === 'undefined') {
- _videoJs2['default'].MediaSource = _videojsContribMediaSources.MediaSource;
- _videoJs2['default'].URL = _videojsContribMediaSources.URL;
- }
- // register source handlers with the appropriate techs
- if (_videojsContribMediaSources.MediaSource.supportsNativeMediaSources()) {
- _videoJs2['default'].getComponent('Html5').registerSourceHandler(HlsSourceHandler('html5'));
- }
- if (window.Uint8Array) {
- _videoJs2['default'].getComponent('Flash').registerSourceHandler(HlsSourceHandler('flash'));
- }
- _videoJs2['default'].HlsHandler = HlsHandler;
- _videoJs2['default'].HlsSourceHandler = HlsSourceHandler;
- _videoJs2['default'].Hls = Hls;
- _videoJs2['default'].m3u8 = _m3u82['default'];
- _videoJs2['default'].registerComponent('Hls', Hls);
- _videoJs2['default'].options.hls = _videoJs2['default'].options.hls || {};
- module.exports = {
- Hls: Hls,
- HlsHandler: HlsHandler,
- HlsSourceHandler: HlsSourceHandler
- };
- }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
- },{"./bin-utils":1,"./decrypter":5,"./m3u8":7,"./master-playlist-controller":11,"./playlist":13,"./playlist-loader":12,"./xhr":19,"global/document":21,"videojs-contrib-media-sources":34}]},{},[57])(57)
- });
|