mux-flv.js 158 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212
  1. /*! @name mux.js @version 6.3.0 @license Apache-2.0 */
  2. (function (global, factory) {
  3. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  4. typeof define === 'function' && define.amd ? define(factory) :
  5. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.muxjs = factory());
  6. }(this, (function () { 'use strict';
  7. /**
  8. * mux.js
  9. *
  10. * Copyright (c) Brightcove
  11. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  12. *
  13. * An object that stores the bytes of an FLV tag and methods for
  14. * querying and manipulating that data.
  15. * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
  16. */
  17. var _FlvTag; // (type:uint, extraData:Boolean = false) extends ByteArray
  18. _FlvTag = function FlvTag(type, extraData) {
  19. var // Counter if this is a metadata tag, nal start marker if this is a video
  20. // tag. unused if this is an audio tag
  21. adHoc = 0,
  22. // :uint
  23. // The default size is 16kb but this is not enough to hold iframe
  24. // data and the resizing algorithm costs a bit so we create a larger
  25. // starting buffer for video tags
  26. bufferStartSize = 16384,
  27. // checks whether the FLV tag has enough capacity to accept the proposed
  28. // write and re-allocates the internal buffers if necessary
  29. prepareWrite = function prepareWrite(flv, count) {
  30. var bytes,
  31. minLength = flv.position + count;
  32. if (minLength < flv.bytes.byteLength) {
  33. // there's enough capacity so do nothing
  34. return;
  35. } // allocate a new buffer and copy over the data that will not be modified
  36. bytes = new Uint8Array(minLength * 2);
  37. bytes.set(flv.bytes.subarray(0, flv.position), 0);
  38. flv.bytes = bytes;
  39. flv.view = new DataView(flv.bytes.buffer);
  40. },
  41. // commonly used metadata properties
  42. widthBytes = _FlvTag.widthBytes || new Uint8Array('width'.length),
  43. heightBytes = _FlvTag.heightBytes || new Uint8Array('height'.length),
  44. videocodecidBytes = _FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length),
  45. i;
  46. if (!_FlvTag.widthBytes) {
  47. // calculating the bytes of common metadata names ahead of time makes the
  48. // corresponding writes faster because we don't have to loop over the
  49. // characters
  50. // re-test with test/perf.html if you're planning on changing this
  51. for (i = 0; i < 'width'.length; i++) {
  52. widthBytes[i] = 'width'.charCodeAt(i);
  53. }
  54. for (i = 0; i < 'height'.length; i++) {
  55. heightBytes[i] = 'height'.charCodeAt(i);
  56. }
  57. for (i = 0; i < 'videocodecid'.length; i++) {
  58. videocodecidBytes[i] = 'videocodecid'.charCodeAt(i);
  59. }
  60. _FlvTag.widthBytes = widthBytes;
  61. _FlvTag.heightBytes = heightBytes;
  62. _FlvTag.videocodecidBytes = videocodecidBytes;
  63. }
  64. this.keyFrame = false; // :Boolean
  65. switch (type) {
  66. case _FlvTag.VIDEO_TAG:
  67. this.length = 16; // Start the buffer at 256k
  68. bufferStartSize *= 6;
  69. break;
  70. case _FlvTag.AUDIO_TAG:
  71. this.length = 13;
  72. this.keyFrame = true;
  73. break;
  74. case _FlvTag.METADATA_TAG:
  75. this.length = 29;
  76. this.keyFrame = true;
  77. break;
  78. default:
  79. throw new Error('Unknown FLV tag type');
  80. }
  81. this.bytes = new Uint8Array(bufferStartSize);
  82. this.view = new DataView(this.bytes.buffer);
  83. this.bytes[0] = type;
  84. this.position = this.length;
  85. this.keyFrame = extraData; // Defaults to false
  86. // presentation timestamp
  87. this.pts = 0; // decoder timestamp
  88. this.dts = 0; // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0)
  89. this.writeBytes = function (bytes, offset, length) {
  90. var start = offset || 0,
  91. end;
  92. length = length || bytes.byteLength;
  93. end = start + length;
  94. prepareWrite(this, length);
  95. this.bytes.set(bytes.subarray(start, end), this.position);
  96. this.position += length;
  97. this.length = Math.max(this.length, this.position);
  98. }; // ByteArray#writeByte(value:int):void
  99. this.writeByte = function (byte) {
  100. prepareWrite(this, 1);
  101. this.bytes[this.position] = byte;
  102. this.position++;
  103. this.length = Math.max(this.length, this.position);
  104. }; // ByteArray#writeShort(value:int):void
  105. this.writeShort = function (short) {
  106. prepareWrite(this, 2);
  107. this.view.setUint16(this.position, short);
  108. this.position += 2;
  109. this.length = Math.max(this.length, this.position);
  110. }; // Negative index into array
  111. // (pos:uint):int
  112. this.negIndex = function (pos) {
  113. return this.bytes[this.length - pos];
  114. }; // The functions below ONLY work when this[0] == VIDEO_TAG.
  115. // We are not going to check for that because we dont want the overhead
  116. // (nal:ByteArray = null):int
  117. this.nalUnitSize = function () {
  118. if (adHoc === 0) {
  119. return 0;
  120. }
  121. return this.length - (adHoc + 4);
  122. };
  123. this.startNalUnit = function () {
  124. // remember position and add 4 bytes
  125. if (adHoc > 0) {
  126. throw new Error('Attempted to create new NAL wihout closing the old one');
  127. } // reserve 4 bytes for nal unit size
  128. adHoc = this.length;
  129. this.length += 4;
  130. this.position = this.length;
  131. }; // (nal:ByteArray = null):void
  132. this.endNalUnit = function (nalContainer) {
  133. var nalStart, // :uint
  134. nalLength; // :uint
  135. // Rewind to the marker and write the size
  136. if (this.length === adHoc + 4) {
  137. // we started a nal unit, but didnt write one, so roll back the 4 byte size value
  138. this.length -= 4;
  139. } else if (adHoc > 0) {
  140. nalStart = adHoc + 4;
  141. nalLength = this.length - nalStart;
  142. this.position = adHoc;
  143. this.view.setUint32(this.position, nalLength);
  144. this.position = this.length;
  145. if (nalContainer) {
  146. // Add the tag to the NAL unit
  147. nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength));
  148. }
  149. }
  150. adHoc = 0;
  151. };
  152. /**
  153. * Write out a 64-bit floating point valued metadata property. This method is
  154. * called frequently during a typical parse and needs to be fast.
  155. */
  156. // (key:String, val:Number):void
  157. this.writeMetaDataDouble = function (key, val) {
  158. var i;
  159. prepareWrite(this, 2 + key.length + 9); // write size of property name
  160. this.view.setUint16(this.position, key.length);
  161. this.position += 2; // this next part looks terrible but it improves parser throughput by
  162. // 10kB/s in my testing
  163. // write property name
  164. if (key === 'width') {
  165. this.bytes.set(widthBytes, this.position);
  166. this.position += 5;
  167. } else if (key === 'height') {
  168. this.bytes.set(heightBytes, this.position);
  169. this.position += 6;
  170. } else if (key === 'videocodecid') {
  171. this.bytes.set(videocodecidBytes, this.position);
  172. this.position += 12;
  173. } else {
  174. for (i = 0; i < key.length; i++) {
  175. this.bytes[this.position] = key.charCodeAt(i);
  176. this.position++;
  177. }
  178. } // skip null byte
  179. this.position++; // write property value
  180. this.view.setFloat64(this.position, val);
  181. this.position += 8; // update flv tag length
  182. this.length = Math.max(this.length, this.position);
  183. ++adHoc;
  184. }; // (key:String, val:Boolean):void
  185. this.writeMetaDataBoolean = function (key, val) {
  186. var i;
  187. prepareWrite(this, 2);
  188. this.view.setUint16(this.position, key.length);
  189. this.position += 2;
  190. for (i = 0; i < key.length; i++) {
  191. // if key.charCodeAt(i) >= 255, handle error
  192. prepareWrite(this, 1);
  193. this.bytes[this.position] = key.charCodeAt(i);
  194. this.position++;
  195. }
  196. prepareWrite(this, 2);
  197. this.view.setUint8(this.position, 0x01);
  198. this.position++;
  199. this.view.setUint8(this.position, val ? 0x01 : 0x00);
  200. this.position++;
  201. this.length = Math.max(this.length, this.position);
  202. ++adHoc;
  203. }; // ():ByteArray
  204. this.finalize = function () {
  205. var dtsDelta, // :int
  206. len; // :int
  207. switch (this.bytes[0]) {
  208. // Video Data
  209. case _FlvTag.VIDEO_TAG:
  210. // We only support AVC, 1 = key frame (for AVC, a seekable
  211. // frame), 2 = inter frame (for AVC, a non-seekable frame)
  212. this.bytes[11] = (this.keyFrame || extraData ? 0x10 : 0x20) | 0x07;
  213. this.bytes[12] = extraData ? 0x00 : 0x01;
  214. dtsDelta = this.pts - this.dts;
  215. this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16;
  216. this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8;
  217. this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0;
  218. break;
  219. case _FlvTag.AUDIO_TAG:
  220. this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo
  221. this.bytes[12] = extraData ? 0x00 : 0x01;
  222. break;
  223. case _FlvTag.METADATA_TAG:
  224. this.position = 11;
  225. this.view.setUint8(this.position, 0x02); // String type
  226. this.position++;
  227. this.view.setUint16(this.position, 0x0A); // 10 Bytes
  228. this.position += 2; // set "onMetaData"
  229. this.bytes.set([0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61], this.position);
  230. this.position += 10;
  231. this.bytes[this.position] = 0x08; // Array type
  232. this.position++;
  233. this.view.setUint32(this.position, adHoc);
  234. this.position = this.length;
  235. this.bytes.set([0, 0, 9], this.position);
  236. this.position += 3; // End Data Tag
  237. this.length = this.position;
  238. break;
  239. }
  240. len = this.length - 11; // write the DataSize field
  241. this.bytes[1] = (len & 0x00FF0000) >>> 16;
  242. this.bytes[2] = (len & 0x0000FF00) >>> 8;
  243. this.bytes[3] = (len & 0x000000FF) >>> 0; // write the Timestamp
  244. this.bytes[4] = (this.dts & 0x00FF0000) >>> 16;
  245. this.bytes[5] = (this.dts & 0x0000FF00) >>> 8;
  246. this.bytes[6] = (this.dts & 0x000000FF) >>> 0;
  247. this.bytes[7] = (this.dts & 0xFF000000) >>> 24; // write the StreamID
  248. this.bytes[8] = 0;
  249. this.bytes[9] = 0;
  250. this.bytes[10] = 0; // Sometimes we're at the end of the view and have one slot to write a
  251. // uint32, so, prepareWrite of count 4, since, view is uint8
  252. prepareWrite(this, 4);
  253. this.view.setUint32(this.length, this.length);
  254. this.length += 4;
  255. this.position += 4; // trim down the byte buffer to what is actually being used
  256. this.bytes = this.bytes.subarray(0, this.length);
  257. this.frameTime = _FlvTag.frameTime(this.bytes); // if bytes.bytelength isn't equal to this.length, handle error
  258. return this;
  259. };
  260. };
  261. _FlvTag.AUDIO_TAG = 0x08; // == 8, :uint
  262. _FlvTag.VIDEO_TAG = 0x09; // == 9, :uint
  263. _FlvTag.METADATA_TAG = 0x12; // == 18, :uint
  264. // (tag:ByteArray):Boolean {
  265. _FlvTag.isAudioFrame = function (tag) {
  266. return _FlvTag.AUDIO_TAG === tag[0];
  267. }; // (tag:ByteArray):Boolean {
  268. _FlvTag.isVideoFrame = function (tag) {
  269. return _FlvTag.VIDEO_TAG === tag[0];
  270. }; // (tag:ByteArray):Boolean {
  271. _FlvTag.isMetaData = function (tag) {
  272. return _FlvTag.METADATA_TAG === tag[0];
  273. }; // (tag:ByteArray):Boolean {
  274. _FlvTag.isKeyFrame = function (tag) {
  275. if (_FlvTag.isVideoFrame(tag)) {
  276. return tag[11] === 0x17;
  277. }
  278. if (_FlvTag.isAudioFrame(tag)) {
  279. return true;
  280. }
  281. if (_FlvTag.isMetaData(tag)) {
  282. return true;
  283. }
  284. return false;
  285. }; // (tag:ByteArray):uint {
  286. _FlvTag.frameTime = function (tag) {
  287. var pts = tag[4] << 16; // :uint
  288. pts |= tag[5] << 8;
  289. pts |= tag[6] << 0;
  290. pts |= tag[7] << 24;
  291. return pts;
  292. };
  293. var flvTag = _FlvTag;
  294. /**
  295. * mux.js
  296. *
  297. * Copyright (c) Brightcove
  298. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  299. *
  300. * A lightweight readable stream implemention that handles event dispatching.
  301. * Objects that inherit from streams should call init in their constructors.
  302. */
  303. var Stream = function Stream() {
  304. this.init = function () {
  305. var listeners = {};
  306. /**
  307. * Add a listener for a specified event type.
  308. * @param type {string} the event name
  309. * @param listener {function} the callback to be invoked when an event of
  310. * the specified type occurs
  311. */
  312. this.on = function (type, listener) {
  313. if (!listeners[type]) {
  314. listeners[type] = [];
  315. }
  316. listeners[type] = listeners[type].concat(listener);
  317. };
  318. /**
  319. * Remove a listener for a specified event type.
  320. * @param type {string} the event name
  321. * @param listener {function} a function previously registered for this
  322. * type of event through `on`
  323. */
  324. this.off = function (type, listener) {
  325. var index;
  326. if (!listeners[type]) {
  327. return false;
  328. }
  329. index = listeners[type].indexOf(listener);
  330. listeners[type] = listeners[type].slice();
  331. listeners[type].splice(index, 1);
  332. return index > -1;
  333. };
  334. /**
  335. * Trigger an event of the specified type on this stream. Any additional
  336. * arguments to this function are passed as parameters to event listeners.
  337. * @param type {string} the event name
  338. */
  339. this.trigger = function (type) {
  340. var callbacks, i, length, args;
  341. callbacks = listeners[type];
  342. if (!callbacks) {
  343. return;
  344. } // Slicing the arguments on every invocation of this method
  345. // can add a significant amount of overhead. Avoid the
  346. // intermediate object creation for the common case of a
  347. // single callback argument
  348. if (arguments.length === 2) {
  349. length = callbacks.length;
  350. for (i = 0; i < length; ++i) {
  351. callbacks[i].call(this, arguments[1]);
  352. }
  353. } else {
  354. args = [];
  355. i = arguments.length;
  356. for (i = 1; i < arguments.length; ++i) {
  357. args.push(arguments[i]);
  358. }
  359. length = callbacks.length;
  360. for (i = 0; i < length; ++i) {
  361. callbacks[i].apply(this, args);
  362. }
  363. }
  364. };
  365. /**
  366. * Destroys the stream and cleans up.
  367. */
  368. this.dispose = function () {
  369. listeners = {};
  370. };
  371. };
  372. };
  373. /**
  374. * Forwards all `data` events on this stream to the destination stream. The
  375. * destination stream should provide a method `push` to receive the data
  376. * events as they arrive.
  377. * @param destination {stream} the stream that will receive all `data` events
  378. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  379. * when the current stream emits a 'done' event
  380. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  381. */
  382. Stream.prototype.pipe = function (destination) {
  383. this.on('data', function (data) {
  384. destination.push(data);
  385. });
  386. this.on('done', function (flushSource) {
  387. destination.flush(flushSource);
  388. });
  389. this.on('partialdone', function (flushSource) {
  390. destination.partialFlush(flushSource);
  391. });
  392. this.on('endedtimeline', function (flushSource) {
  393. destination.endTimeline(flushSource);
  394. });
  395. this.on('reset', function (flushSource) {
  396. destination.reset(flushSource);
  397. });
  398. return destination;
  399. }; // Default stream functions that are expected to be overridden to perform
  400. // actual work. These are provided by the prototype as a sort of no-op
  401. // implementation so that we don't have to check for their existence in the
  402. // `pipe` function above.
  403. Stream.prototype.push = function (data) {
  404. this.trigger('data', data);
  405. };
  406. Stream.prototype.flush = function (flushSource) {
  407. this.trigger('done', flushSource);
  408. };
  409. Stream.prototype.partialFlush = function (flushSource) {
  410. this.trigger('partialdone', flushSource);
  411. };
  412. Stream.prototype.endTimeline = function (flushSource) {
  413. this.trigger('endedtimeline', flushSource);
  414. };
  415. Stream.prototype.reset = function (flushSource) {
  416. this.trigger('reset', flushSource);
  417. };
  418. var stream = Stream;
  419. /**
  420. * mux.js
  421. *
  422. * Copyright (c) Brightcove
  423. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  424. *
  425. * Reads in-band caption information from a video elementary
  426. * stream. Captions must follow the CEA-708 standard for injection
  427. * into an MPEG-2 transport streams.
  428. * @see https://en.wikipedia.org/wiki/CEA-708
  429. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  430. */
  431. // payload type field to indicate how they are to be
  432. // interpreted. CEAS-708 caption content is always transmitted with
  433. // payload type 0x04.
  434. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  435. RBSP_TRAILING_BITS = 128;
  436. /**
  437. * Parse a supplemental enhancement information (SEI) NAL unit.
  438. * Stops parsing once a message of type ITU T T35 has been found.
  439. *
  440. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  441. * @return {object} the parsed SEI payload
  442. * @see Rec. ITU-T H.264, 7.3.2.3.1
  443. */
  444. var parseSei = function parseSei(bytes) {
  445. var i = 0,
  446. result = {
  447. payloadType: -1,
  448. payloadSize: 0
  449. },
  450. payloadType = 0,
  451. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  452. while (i < bytes.byteLength) {
  453. // stop once we have hit the end of the sei_rbsp
  454. if (bytes[i] === RBSP_TRAILING_BITS) {
  455. break;
  456. } // Parse payload type
  457. while (bytes[i] === 0xFF) {
  458. payloadType += 255;
  459. i++;
  460. }
  461. payloadType += bytes[i++]; // Parse payload size
  462. while (bytes[i] === 0xFF) {
  463. payloadSize += 255;
  464. i++;
  465. }
  466. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  467. // there can only ever be one caption message in a frame's sei
  468. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  469. var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
  470. if (userIdentifier === 'GA94') {
  471. result.payloadType = payloadType;
  472. result.payloadSize = payloadSize;
  473. result.payload = bytes.subarray(i, i + payloadSize);
  474. break;
  475. } else {
  476. result.payload = void 0;
  477. }
  478. } // skip the payload and parse the next message
  479. i += payloadSize;
  480. payloadType = 0;
  481. payloadSize = 0;
  482. }
  483. return result;
  484. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  485. var parseUserData = function parseUserData(sei) {
  486. // itu_t_t35_contry_code must be 181 (United States) for
  487. // captions
  488. if (sei.payload[0] !== 181) {
  489. return null;
  490. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  491. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  492. return null;
  493. } // the user_identifier should be "GA94" to indicate ATSC1 data
  494. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  495. return null;
  496. } // finally, user_data_type_code should be 0x03 for caption data
  497. if (sei.payload[7] !== 0x03) {
  498. return null;
  499. } // return the user_data_type_structure and strip the trailing
  500. // marker bits
  501. return sei.payload.subarray(8, sei.payload.length - 1);
  502. }; // see CEA-708-D, section 4.4
  503. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  504. var results = [],
  505. i,
  506. count,
  507. offset,
  508. data; // if this is just filler, return immediately
  509. if (!(userData[0] & 0x40)) {
  510. return results;
  511. } // parse out the cc_data_1 and cc_data_2 fields
  512. count = userData[0] & 0x1f;
  513. for (i = 0; i < count; i++) {
  514. offset = i * 3;
  515. data = {
  516. type: userData[offset + 2] & 0x03,
  517. pts: pts
  518. }; // capture cc data when cc_valid is 1
  519. if (userData[offset + 2] & 0x04) {
  520. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  521. results.push(data);
  522. }
  523. }
  524. return results;
  525. };
  526. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  527. var length = data.byteLength,
  528. emulationPreventionBytesPositions = [],
  529. i = 1,
  530. newLength,
  531. newData; // Find all `Emulation Prevention Bytes`
  532. while (i < length - 2) {
  533. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  534. emulationPreventionBytesPositions.push(i + 2);
  535. i += 2;
  536. } else {
  537. i++;
  538. }
  539. } // If no Emulation Prevention Bytes were found just return the original
  540. // array
  541. if (emulationPreventionBytesPositions.length === 0) {
  542. return data;
  543. } // Create a new array to hold the NAL unit data
  544. newLength = length - emulationPreventionBytesPositions.length;
  545. newData = new Uint8Array(newLength);
  546. var sourceIndex = 0;
  547. for (i = 0; i < newLength; sourceIndex++, i++) {
  548. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  549. // Skip this byte
  550. sourceIndex++; // Remove this position index
  551. emulationPreventionBytesPositions.shift();
  552. }
  553. newData[i] = data[sourceIndex];
  554. }
  555. return newData;
  556. }; // exports
  557. var captionPacketParser = {
  558. parseSei: parseSei,
  559. parseUserData: parseUserData,
  560. parseCaptionPackets: parseCaptionPackets,
  561. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  562. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  563. };
  564. // Link To Transport
  565. // -----------------
  566. var CaptionStream = function CaptionStream(options) {
  567. options = options || {};
  568. CaptionStream.prototype.init.call(this); // parse708captions flag, default to true
  569. this.parse708captions_ = typeof options.parse708captions === 'boolean' ? options.parse708captions : true;
  570. this.captionPackets_ = [];
  571. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  572. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  573. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  574. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  575. ];
  576. if (this.parse708captions_) {
  577. this.cc708Stream_ = new Cea708Stream({
  578. captionServices: options.captionServices
  579. }); // eslint-disable-line no-use-before-define
  580. }
  581. this.reset(); // forward data and done events from CCs to this CaptionStream
  582. this.ccStreams_.forEach(function (cc) {
  583. cc.on('data', this.trigger.bind(this, 'data'));
  584. cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
  585. cc.on('done', this.trigger.bind(this, 'done'));
  586. }, this);
  587. if (this.parse708captions_) {
  588. this.cc708Stream_.on('data', this.trigger.bind(this, 'data'));
  589. this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone'));
  590. this.cc708Stream_.on('done', this.trigger.bind(this, 'done'));
  591. }
  592. };
  593. CaptionStream.prototype = new stream();
  594. CaptionStream.prototype.push = function (event) {
  595. var sei, userData, newCaptionPackets; // only examine SEI NALs
  596. if (event.nalUnitType !== 'sei_rbsp') {
  597. return;
  598. } // parse the sei
  599. sei = captionPacketParser.parseSei(event.escapedRBSP); // no payload data, skip
  600. if (!sei.payload) {
  601. return;
  602. } // ignore everything but user_data_registered_itu_t_t35
  603. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  604. return;
  605. } // parse out the user data payload
  606. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  607. if (!userData) {
  608. return;
  609. } // Sometimes, the same segment # will be downloaded twice. To stop the
  610. // caption data from being processed twice, we track the latest dts we've
  611. // received and ignore everything with a dts before that. However, since
  612. // data for a specific dts can be split across packets on either side of
  613. // a segment boundary, we need to make sure we *don't* ignore the packets
  614. // from the *next* segment that have dts === this.latestDts_. By constantly
  615. // tracking the number of packets received with dts === this.latestDts_, we
  616. // know how many should be ignored once we start receiving duplicates.
  617. if (event.dts < this.latestDts_) {
  618. // We've started getting older data, so set the flag.
  619. this.ignoreNextEqualDts_ = true;
  620. return;
  621. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  622. this.numSameDts_--;
  623. if (!this.numSameDts_) {
  624. // We've received the last duplicate packet, time to start processing again
  625. this.ignoreNextEqualDts_ = false;
  626. }
  627. return;
  628. } // parse out CC data packets and save them for later
  629. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  630. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  631. if (this.latestDts_ !== event.dts) {
  632. this.numSameDts_ = 0;
  633. }
  634. this.numSameDts_++;
  635. this.latestDts_ = event.dts;
  636. };
  637. CaptionStream.prototype.flushCCStreams = function (flushType) {
  638. this.ccStreams_.forEach(function (cc) {
  639. return flushType === 'flush' ? cc.flush() : cc.partialFlush();
  640. }, this);
  641. };
  642. CaptionStream.prototype.flushStream = function (flushType) {
  643. // make sure we actually parsed captions before proceeding
  644. if (!this.captionPackets_.length) {
  645. this.flushCCStreams(flushType);
  646. return;
  647. } // In Chrome, the Array#sort function is not stable so add a
  648. // presortIndex that we can use to ensure we get a stable-sort
  649. this.captionPackets_.forEach(function (elem, idx) {
  650. elem.presortIndex = idx;
  651. }); // sort caption byte-pairs based on their PTS values
  652. this.captionPackets_.sort(function (a, b) {
  653. if (a.pts === b.pts) {
  654. return a.presortIndex - b.presortIndex;
  655. }
  656. return a.pts - b.pts;
  657. });
  658. this.captionPackets_.forEach(function (packet) {
  659. if (packet.type < 2) {
  660. // Dispatch packet to the right Cea608Stream
  661. this.dispatchCea608Packet(packet);
  662. } else {
  663. // Dispatch packet to the Cea708Stream
  664. this.dispatchCea708Packet(packet);
  665. }
  666. }, this);
  667. this.captionPackets_.length = 0;
  668. this.flushCCStreams(flushType);
  669. };
  670. CaptionStream.prototype.flush = function () {
  671. return this.flushStream('flush');
  672. }; // Only called if handling partial data
  673. CaptionStream.prototype.partialFlush = function () {
  674. return this.flushStream('partialFlush');
  675. };
  676. CaptionStream.prototype.reset = function () {
  677. this.latestDts_ = null;
  678. this.ignoreNextEqualDts_ = false;
  679. this.numSameDts_ = 0;
  680. this.activeCea608Channel_ = [null, null];
  681. this.ccStreams_.forEach(function (ccStream) {
  682. ccStream.reset();
  683. });
  684. }; // From the CEA-608 spec:
  685. /*
  686. * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
  687. * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
  688. * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
  689. * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
  690. * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
  691. * to switch to captioning or Text.
  692. */
  693. // With that in mind, we ignore any data between an XDS control code and a
  694. // subsequent closed-captioning control code.
  695. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  696. // NOTE: packet.type is the CEA608 field
  697. if (this.setsTextOrXDSActive(packet)) {
  698. this.activeCea608Channel_[packet.type] = null;
  699. } else if (this.setsChannel1Active(packet)) {
  700. this.activeCea608Channel_[packet.type] = 0;
  701. } else if (this.setsChannel2Active(packet)) {
  702. this.activeCea608Channel_[packet.type] = 1;
  703. }
  704. if (this.activeCea608Channel_[packet.type] === null) {
  705. // If we haven't received anything to set the active channel, or the
  706. // packets are Text/XDS data, discard the data; we don't want jumbled
  707. // captions
  708. return;
  709. }
  710. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  711. };
  712. CaptionStream.prototype.setsChannel1Active = function (packet) {
  713. return (packet.ccData & 0x7800) === 0x1000;
  714. };
  715. CaptionStream.prototype.setsChannel2Active = function (packet) {
  716. return (packet.ccData & 0x7800) === 0x1800;
  717. };
  718. CaptionStream.prototype.setsTextOrXDSActive = function (packet) {
  719. return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
  720. };
  721. CaptionStream.prototype.dispatchCea708Packet = function (packet) {
  722. if (this.parse708captions_) {
  723. this.cc708Stream_.push(packet);
  724. }
  725. }; // ----------------------
  726. // Session to Application
  727. // ----------------------
  728. // This hash maps special and extended character codes to their
  729. // proper Unicode equivalent. The first one-byte key is just a
  730. // non-standard character code. The two-byte keys that follow are
  731. // the extended CEA708 character codes, along with the preceding
  732. // 0x10 extended character byte to distinguish these codes from
  733. // non-extended character codes. Every CEA708 character code that
  734. // is not in this object maps directly to a standard unicode
  735. // character code.
  736. // The transparent space and non-breaking transparent space are
  737. // technically not fully supported since there is no code to
  738. // make them transparent, so they have normal non-transparent
  739. // stand-ins.
  740. // The special closed caption (CC) character isn't a standard
  741. // unicode character, so a fairly similar unicode character was
  742. // chosen in it's place.
  743. var CHARACTER_TRANSLATION_708 = {
  744. 0x7f: 0x266a,
  745. // ♪
  746. 0x1020: 0x20,
  747. // Transparent Space
  748. 0x1021: 0xa0,
  749. // Nob-breaking Transparent Space
  750. 0x1025: 0x2026,
  751. // …
  752. 0x102a: 0x0160,
  753. // Š
  754. 0x102c: 0x0152,
  755. // Œ
  756. 0x1030: 0x2588,
  757. // █
  758. 0x1031: 0x2018,
  759. // ‘
  760. 0x1032: 0x2019,
  761. // ’
  762. 0x1033: 0x201c,
  763. // “
  764. 0x1034: 0x201d,
  765. // ”
  766. 0x1035: 0x2022,
  767. // •
  768. 0x1039: 0x2122,
  769. // ™
  770. 0x103a: 0x0161,
  771. // š
  772. 0x103c: 0x0153,
  773. // œ
  774. 0x103d: 0x2120,
  775. // ℠
  776. 0x103f: 0x0178,
  777. // Ÿ
  778. 0x1076: 0x215b,
  779. // ⅛
  780. 0x1077: 0x215c,
  781. // ⅜
  782. 0x1078: 0x215d,
  783. // ⅝
  784. 0x1079: 0x215e,
  785. // ⅞
  786. 0x107a: 0x23d0,
  787. // ⏐
  788. 0x107b: 0x23a4,
  789. // ⎤
  790. 0x107c: 0x23a3,
  791. // ⎣
  792. 0x107d: 0x23af,
  793. // ⎯
  794. 0x107e: 0x23a6,
  795. // ⎦
  796. 0x107f: 0x23a1,
  797. // ⎡
  798. 0x10a0: 0x3138 // ㄸ (CC char)
  799. };
  800. var get708CharFromCode = function get708CharFromCode(code) {
  801. var newCode = CHARACTER_TRANSLATION_708[code] || code;
  802. if (code & 0x1000 && code === newCode) {
  803. // Invalid extended code
  804. return '';
  805. }
  806. return String.fromCharCode(newCode);
  807. };
  808. var within708TextBlock = function within708TextBlock(b) {
  809. return 0x20 <= b && b <= 0x7f || 0xa0 <= b && b <= 0xff;
  810. };
  811. var Cea708Window = function Cea708Window(windowNum) {
  812. this.windowNum = windowNum;
  813. this.reset();
  814. };
  815. Cea708Window.prototype.reset = function () {
  816. this.clearText();
  817. this.pendingNewLine = false;
  818. this.winAttr = {};
  819. this.penAttr = {};
  820. this.penLoc = {};
  821. this.penColor = {}; // These default values are arbitrary,
  822. // defineWindow will usually override them
  823. this.visible = 0;
  824. this.rowLock = 0;
  825. this.columnLock = 0;
  826. this.priority = 0;
  827. this.relativePositioning = 0;
  828. this.anchorVertical = 0;
  829. this.anchorHorizontal = 0;
  830. this.anchorPoint = 0;
  831. this.rowCount = 1;
  832. this.virtualRowCount = this.rowCount + 1;
  833. this.columnCount = 41;
  834. this.windowStyle = 0;
  835. this.penStyle = 0;
  836. };
  837. Cea708Window.prototype.getText = function () {
  838. return this.rows.join('\n');
  839. };
  840. Cea708Window.prototype.clearText = function () {
  841. this.rows = [''];
  842. this.rowIdx = 0;
  843. };
  844. Cea708Window.prototype.newLine = function (pts) {
  845. if (this.rows.length >= this.virtualRowCount && typeof this.beforeRowOverflow === 'function') {
  846. this.beforeRowOverflow(pts);
  847. }
  848. if (this.rows.length > 0) {
  849. this.rows.push('');
  850. this.rowIdx++;
  851. } // Show all virtual rows since there's no visible scrolling
  852. while (this.rows.length > this.virtualRowCount) {
  853. this.rows.shift();
  854. this.rowIdx--;
  855. }
  856. };
  857. Cea708Window.prototype.isEmpty = function () {
  858. if (this.rows.length === 0) {
  859. return true;
  860. } else if (this.rows.length === 1) {
  861. return this.rows[0] === '';
  862. }
  863. return false;
  864. };
  865. Cea708Window.prototype.addText = function (text) {
  866. this.rows[this.rowIdx] += text;
  867. };
  868. Cea708Window.prototype.backspace = function () {
  869. if (!this.isEmpty()) {
  870. var row = this.rows[this.rowIdx];
  871. this.rows[this.rowIdx] = row.substr(0, row.length - 1);
  872. }
  873. };
  874. var Cea708Service = function Cea708Service(serviceNum, encoding, stream) {
  875. this.serviceNum = serviceNum;
  876. this.text = '';
  877. this.currentWindow = new Cea708Window(-1);
  878. this.windows = [];
  879. this.stream = stream; // Try to setup a TextDecoder if an `encoding` value was provided
  880. if (typeof encoding === 'string') {
  881. this.createTextDecoder(encoding);
  882. }
  883. };
  884. /**
  885. * Initialize service windows
  886. * Must be run before service use
  887. *
  888. * @param {Integer} pts PTS value
  889. * @param {Function} beforeRowOverflow Function to execute before row overflow of a window
  890. */
  891. Cea708Service.prototype.init = function (pts, beforeRowOverflow) {
  892. this.startPts = pts;
  893. for (var win = 0; win < 8; win++) {
  894. this.windows[win] = new Cea708Window(win);
  895. if (typeof beforeRowOverflow === 'function') {
  896. this.windows[win].beforeRowOverflow = beforeRowOverflow;
  897. }
  898. }
  899. };
  900. /**
  901. * Set current window of service to be affected by commands
  902. *
  903. * @param {Integer} windowNum Window number
  904. */
  905. Cea708Service.prototype.setCurrentWindow = function (windowNum) {
  906. this.currentWindow = this.windows[windowNum];
  907. };
  908. /**
  909. * Try to create a TextDecoder if it is natively supported
  910. */
  911. Cea708Service.prototype.createTextDecoder = function (encoding) {
  912. if (typeof TextDecoder === 'undefined') {
  913. this.stream.trigger('log', {
  914. level: 'warn',
  915. message: 'The `encoding` option is unsupported without TextDecoder support'
  916. });
  917. } else {
  918. try {
  919. this.textDecoder_ = new TextDecoder(encoding);
  920. } catch (error) {
  921. this.stream.trigger('log', {
  922. level: 'warn',
  923. message: 'TextDecoder could not be created with ' + encoding + ' encoding. ' + error
  924. });
  925. }
  926. }
  927. };
  928. var Cea708Stream = function Cea708Stream(options) {
  929. options = options || {};
  930. Cea708Stream.prototype.init.call(this);
  931. var self = this;
  932. var captionServices = options.captionServices || {};
  933. var captionServiceEncodings = {};
  934. var serviceProps; // Get service encodings from captionServices option block
  935. Object.keys(captionServices).forEach(function (serviceName) {
  936. serviceProps = captionServices[serviceName];
  937. if (/^SERVICE/.test(serviceName)) {
  938. captionServiceEncodings[serviceName] = serviceProps.encoding;
  939. }
  940. });
  941. this.serviceEncodings = captionServiceEncodings;
  942. this.current708Packet = null;
  943. this.services = {};
  944. this.push = function (packet) {
  945. if (packet.type === 3) {
  946. // 708 packet start
  947. self.new708Packet();
  948. self.add708Bytes(packet);
  949. } else {
  950. if (self.current708Packet === null) {
  951. // This should only happen at the start of a file if there's no packet start.
  952. self.new708Packet();
  953. }
  954. self.add708Bytes(packet);
  955. }
  956. };
  957. };
  958. Cea708Stream.prototype = new stream();
  959. /**
  960. * Push current 708 packet, create new 708 packet.
  961. */
  962. Cea708Stream.prototype.new708Packet = function () {
  963. if (this.current708Packet !== null) {
  964. this.push708Packet();
  965. }
  966. this.current708Packet = {
  967. data: [],
  968. ptsVals: []
  969. };
  970. };
  971. /**
  972. * Add pts and both bytes from packet into current 708 packet.
  973. */
  974. Cea708Stream.prototype.add708Bytes = function (packet) {
  975. var data = packet.ccData;
  976. var byte0 = data >>> 8;
  977. var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec
  978. // that service blocks will always line up with byte pairs.
  979. this.current708Packet.ptsVals.push(packet.pts);
  980. this.current708Packet.data.push(byte0);
  981. this.current708Packet.data.push(byte1);
  982. };
  983. /**
  984. * Parse completed 708 packet into service blocks and push each service block.
  985. */
  986. Cea708Stream.prototype.push708Packet = function () {
  987. var packet708 = this.current708Packet;
  988. var packetData = packet708.data;
  989. var serviceNum = null;
  990. var blockSize = null;
  991. var i = 0;
  992. var b = packetData[i++];
  993. packet708.seq = b >> 6;
  994. packet708.sizeCode = b & 0x3f; // 0b00111111;
  995. for (; i < packetData.length; i++) {
  996. b = packetData[i++];
  997. serviceNum = b >> 5;
  998. blockSize = b & 0x1f; // 0b00011111
  999. if (serviceNum === 7 && blockSize > 0) {
  1000. // Extended service num
  1001. b = packetData[i++];
  1002. serviceNum = b;
  1003. }
  1004. this.pushServiceBlock(serviceNum, i, blockSize);
  1005. if (blockSize > 0) {
  1006. i += blockSize - 1;
  1007. }
  1008. }
  1009. };
  1010. /**
  1011. * Parse service block, execute commands, read text.
  1012. *
  1013. * Note: While many of these commands serve important purposes,
  1014. * many others just parse out the parameters or attributes, but
  1015. * nothing is done with them because this is not a full and complete
  1016. * implementation of the entire 708 spec.
  1017. *
  1018. * @param {Integer} serviceNum Service number
  1019. * @param {Integer} start Start index of the 708 packet data
  1020. * @param {Integer} size Block size
  1021. */
  1022. Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) {
  1023. var b;
  1024. var i = start;
  1025. var packetData = this.current708Packet.data;
  1026. var service = this.services[serviceNum];
  1027. if (!service) {
  1028. service = this.initService(serviceNum, i);
  1029. }
  1030. for (; i < start + size && i < packetData.length; i++) {
  1031. b = packetData[i];
  1032. if (within708TextBlock(b)) {
  1033. i = this.handleText(i, service);
  1034. } else if (b === 0x18) {
  1035. i = this.multiByteCharacter(i, service);
  1036. } else if (b === 0x10) {
  1037. i = this.extendedCommands(i, service);
  1038. } else if (0x80 <= b && b <= 0x87) {
  1039. i = this.setCurrentWindow(i, service);
  1040. } else if (0x98 <= b && b <= 0x9f) {
  1041. i = this.defineWindow(i, service);
  1042. } else if (b === 0x88) {
  1043. i = this.clearWindows(i, service);
  1044. } else if (b === 0x8c) {
  1045. i = this.deleteWindows(i, service);
  1046. } else if (b === 0x89) {
  1047. i = this.displayWindows(i, service);
  1048. } else if (b === 0x8a) {
  1049. i = this.hideWindows(i, service);
  1050. } else if (b === 0x8b) {
  1051. i = this.toggleWindows(i, service);
  1052. } else if (b === 0x97) {
  1053. i = this.setWindowAttributes(i, service);
  1054. } else if (b === 0x90) {
  1055. i = this.setPenAttributes(i, service);
  1056. } else if (b === 0x91) {
  1057. i = this.setPenColor(i, service);
  1058. } else if (b === 0x92) {
  1059. i = this.setPenLocation(i, service);
  1060. } else if (b === 0x8f) {
  1061. service = this.reset(i, service);
  1062. } else if (b === 0x08) {
  1063. // BS: Backspace
  1064. service.currentWindow.backspace();
  1065. } else if (b === 0x0c) {
  1066. // FF: Form feed
  1067. service.currentWindow.clearText();
  1068. } else if (b === 0x0d) {
  1069. // CR: Carriage return
  1070. service.currentWindow.pendingNewLine = true;
  1071. } else if (b === 0x0e) {
  1072. // HCR: Horizontal carriage return
  1073. service.currentWindow.clearText();
  1074. } else if (b === 0x8d) {
  1075. // DLY: Delay, nothing to do
  1076. i++;
  1077. } else ;
  1078. }
  1079. };
  1080. /**
  1081. * Execute an extended command
  1082. *
  1083. * @param {Integer} i Current index in the 708 packet
  1084. * @param {Service} service The service object to be affected
  1085. * @return {Integer} New index after parsing
  1086. */
  1087. Cea708Stream.prototype.extendedCommands = function (i, service) {
  1088. var packetData = this.current708Packet.data;
  1089. var b = packetData[++i];
  1090. if (within708TextBlock(b)) {
  1091. i = this.handleText(i, service, {
  1092. isExtended: true
  1093. });
  1094. }
  1095. return i;
  1096. };
  1097. /**
  1098. * Get PTS value of a given byte index
  1099. *
  1100. * @param {Integer} byteIndex Index of the byte
  1101. * @return {Integer} PTS
  1102. */
  1103. Cea708Stream.prototype.getPts = function (byteIndex) {
  1104. // There's 1 pts value per 2 bytes
  1105. return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)];
  1106. };
  1107. /**
  1108. * Initializes a service
  1109. *
  1110. * @param {Integer} serviceNum Service number
  1111. * @return {Service} Initialized service object
  1112. */
  1113. Cea708Stream.prototype.initService = function (serviceNum, i) {
  1114. var serviceName = 'SERVICE' + serviceNum;
  1115. var self = this;
  1116. var serviceName;
  1117. var encoding;
  1118. if (serviceName in this.serviceEncodings) {
  1119. encoding = this.serviceEncodings[serviceName];
  1120. }
  1121. this.services[serviceNum] = new Cea708Service(serviceNum, encoding, self);
  1122. this.services[serviceNum].init(this.getPts(i), function (pts) {
  1123. self.flushDisplayed(pts, self.services[serviceNum]);
  1124. });
  1125. return this.services[serviceNum];
  1126. };
  1127. /**
  1128. * Execute text writing to current window
  1129. *
  1130. * @param {Integer} i Current index in the 708 packet
  1131. * @param {Service} service The service object to be affected
  1132. * @return {Integer} New index after parsing
  1133. */
  1134. Cea708Stream.prototype.handleText = function (i, service, options) {
  1135. var isExtended = options && options.isExtended;
  1136. var isMultiByte = options && options.isMultiByte;
  1137. var packetData = this.current708Packet.data;
  1138. var extended = isExtended ? 0x1000 : 0x0000;
  1139. var currentByte = packetData[i];
  1140. var nextByte = packetData[i + 1];
  1141. var win = service.currentWindow;
  1142. var char;
  1143. var charCodeArray; // Use the TextDecoder if one was created for this service
  1144. if (service.textDecoder_ && !isExtended) {
  1145. if (isMultiByte) {
  1146. charCodeArray = [currentByte, nextByte];
  1147. i++;
  1148. } else {
  1149. charCodeArray = [currentByte];
  1150. }
  1151. char = service.textDecoder_.decode(new Uint8Array(charCodeArray));
  1152. } else {
  1153. char = get708CharFromCode(extended | currentByte);
  1154. }
  1155. if (win.pendingNewLine && !win.isEmpty()) {
  1156. win.newLine(this.getPts(i));
  1157. }
  1158. win.pendingNewLine = false;
  1159. win.addText(char);
  1160. return i;
  1161. };
  1162. /**
  1163. * Handle decoding of multibyte character
  1164. *
  1165. * @param {Integer} i Current index in the 708 packet
  1166. * @param {Service} service The service object to be affected
  1167. * @return {Integer} New index after parsing
  1168. */
  1169. Cea708Stream.prototype.multiByteCharacter = function (i, service) {
  1170. var packetData = this.current708Packet.data;
  1171. var firstByte = packetData[i + 1];
  1172. var secondByte = packetData[i + 2];
  1173. if (within708TextBlock(firstByte) && within708TextBlock(secondByte)) {
  1174. i = this.handleText(++i, service, {
  1175. isMultiByte: true
  1176. });
  1177. }
  1178. return i;
  1179. };
  1180. /**
  1181. * Parse and execute the CW# command.
  1182. *
  1183. * Set the current window.
  1184. *
  1185. * @param {Integer} i Current index in the 708 packet
  1186. * @param {Service} service The service object to be affected
  1187. * @return {Integer} New index after parsing
  1188. */
  1189. Cea708Stream.prototype.setCurrentWindow = function (i, service) {
  1190. var packetData = this.current708Packet.data;
  1191. var b = packetData[i];
  1192. var windowNum = b & 0x07;
  1193. service.setCurrentWindow(windowNum);
  1194. return i;
  1195. };
  1196. /**
  1197. * Parse and execute the DF# command.
  1198. *
  1199. * Define a window and set it as the current window.
  1200. *
  1201. * @param {Integer} i Current index in the 708 packet
  1202. * @param {Service} service The service object to be affected
  1203. * @return {Integer} New index after parsing
  1204. */
  1205. Cea708Stream.prototype.defineWindow = function (i, service) {
  1206. var packetData = this.current708Packet.data;
  1207. var b = packetData[i];
  1208. var windowNum = b & 0x07;
  1209. service.setCurrentWindow(windowNum);
  1210. var win = service.currentWindow;
  1211. b = packetData[++i];
  1212. win.visible = (b & 0x20) >> 5; // v
  1213. win.rowLock = (b & 0x10) >> 4; // rl
  1214. win.columnLock = (b & 0x08) >> 3; // cl
  1215. win.priority = b & 0x07; // p
  1216. b = packetData[++i];
  1217. win.relativePositioning = (b & 0x80) >> 7; // rp
  1218. win.anchorVertical = b & 0x7f; // av
  1219. b = packetData[++i];
  1220. win.anchorHorizontal = b; // ah
  1221. b = packetData[++i];
  1222. win.anchorPoint = (b & 0xf0) >> 4; // ap
  1223. win.rowCount = b & 0x0f; // rc
  1224. b = packetData[++i];
  1225. win.columnCount = b & 0x3f; // cc
  1226. b = packetData[++i];
  1227. win.windowStyle = (b & 0x38) >> 3; // ws
  1228. win.penStyle = b & 0x07; // ps
  1229. // The spec says there are (rowCount+1) "virtual rows"
  1230. win.virtualRowCount = win.rowCount + 1;
  1231. return i;
  1232. };
  1233. /**
  1234. * Parse and execute the SWA command.
  1235. *
  1236. * Set attributes of the current window.
  1237. *
  1238. * @param {Integer} i Current index in the 708 packet
  1239. * @param {Service} service The service object to be affected
  1240. * @return {Integer} New index after parsing
  1241. */
  1242. Cea708Stream.prototype.setWindowAttributes = function (i, service) {
  1243. var packetData = this.current708Packet.data;
  1244. var b = packetData[i];
  1245. var winAttr = service.currentWindow.winAttr;
  1246. b = packetData[++i];
  1247. winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
  1248. winAttr.fillRed = (b & 0x30) >> 4; // fr
  1249. winAttr.fillGreen = (b & 0x0c) >> 2; // fg
  1250. winAttr.fillBlue = b & 0x03; // fb
  1251. b = packetData[++i];
  1252. winAttr.borderType = (b & 0xc0) >> 6; // bt
  1253. winAttr.borderRed = (b & 0x30) >> 4; // br
  1254. winAttr.borderGreen = (b & 0x0c) >> 2; // bg
  1255. winAttr.borderBlue = b & 0x03; // bb
  1256. b = packetData[++i];
  1257. winAttr.borderType += (b & 0x80) >> 5; // bt
  1258. winAttr.wordWrap = (b & 0x40) >> 6; // ww
  1259. winAttr.printDirection = (b & 0x30) >> 4; // pd
  1260. winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
  1261. winAttr.justify = b & 0x03; // j
  1262. b = packetData[++i];
  1263. winAttr.effectSpeed = (b & 0xf0) >> 4; // es
  1264. winAttr.effectDirection = (b & 0x0c) >> 2; // ed
  1265. winAttr.displayEffect = b & 0x03; // de
  1266. return i;
  1267. };
  1268. /**
  1269. * Gather text from all displayed windows and push a caption to output.
  1270. *
  1271. * @param {Integer} i Current index in the 708 packet
  1272. * @param {Service} service The service object to be affected
  1273. */
  1274. Cea708Stream.prototype.flushDisplayed = function (pts, service) {
  1275. var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
  1276. // display text in the correct order, but sample files so far have not shown any issue.
  1277. for (var winId = 0; winId < 8; winId++) {
  1278. if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
  1279. displayedText.push(service.windows[winId].getText());
  1280. }
  1281. }
  1282. service.endPts = pts;
  1283. service.text = displayedText.join('\n\n');
  1284. this.pushCaption(service);
  1285. service.startPts = pts;
  1286. };
  1287. /**
  1288. * Push a caption to output if the caption contains text.
  1289. *
  1290. * @param {Service} service The service object to be affected
  1291. */
  1292. Cea708Stream.prototype.pushCaption = function (service) {
  1293. if (service.text !== '') {
  1294. this.trigger('data', {
  1295. startPts: service.startPts,
  1296. endPts: service.endPts,
  1297. text: service.text,
  1298. stream: 'cc708_' + service.serviceNum
  1299. });
  1300. service.text = '';
  1301. service.startPts = service.endPts;
  1302. }
  1303. };
  1304. /**
  1305. * Parse and execute the DSW command.
  1306. *
  1307. * Set visible property of windows based on the parsed bitmask.
  1308. *
  1309. * @param {Integer} i Current index in the 708 packet
  1310. * @param {Service} service The service object to be affected
  1311. * @return {Integer} New index after parsing
  1312. */
  1313. Cea708Stream.prototype.displayWindows = function (i, service) {
  1314. var packetData = this.current708Packet.data;
  1315. var b = packetData[++i];
  1316. var pts = this.getPts(i);
  1317. this.flushDisplayed(pts, service);
  1318. for (var winId = 0; winId < 8; winId++) {
  1319. if (b & 0x01 << winId) {
  1320. service.windows[winId].visible = 1;
  1321. }
  1322. }
  1323. return i;
  1324. };
  1325. /**
  1326. * Parse and execute the HDW command.
  1327. *
  1328. * Set visible property of windows based on the parsed bitmask.
  1329. *
  1330. * @param {Integer} i Current index in the 708 packet
  1331. * @param {Service} service The service object to be affected
  1332. * @return {Integer} New index after parsing
  1333. */
  1334. Cea708Stream.prototype.hideWindows = function (i, service) {
  1335. var packetData = this.current708Packet.data;
  1336. var b = packetData[++i];
  1337. var pts = this.getPts(i);
  1338. this.flushDisplayed(pts, service);
  1339. for (var winId = 0; winId < 8; winId++) {
  1340. if (b & 0x01 << winId) {
  1341. service.windows[winId].visible = 0;
  1342. }
  1343. }
  1344. return i;
  1345. };
  1346. /**
  1347. * Parse and execute the TGW command.
  1348. *
  1349. * Set visible property of windows based on the parsed bitmask.
  1350. *
  1351. * @param {Integer} i Current index in the 708 packet
  1352. * @param {Service} service The service object to be affected
  1353. * @return {Integer} New index after parsing
  1354. */
  1355. Cea708Stream.prototype.toggleWindows = function (i, service) {
  1356. var packetData = this.current708Packet.data;
  1357. var b = packetData[++i];
  1358. var pts = this.getPts(i);
  1359. this.flushDisplayed(pts, service);
  1360. for (var winId = 0; winId < 8; winId++) {
  1361. if (b & 0x01 << winId) {
  1362. service.windows[winId].visible ^= 1;
  1363. }
  1364. }
  1365. return i;
  1366. };
  1367. /**
  1368. * Parse and execute the CLW command.
  1369. *
  1370. * Clear text of windows based on the parsed bitmask.
  1371. *
  1372. * @param {Integer} i Current index in the 708 packet
  1373. * @param {Service} service The service object to be affected
  1374. * @return {Integer} New index after parsing
  1375. */
  1376. Cea708Stream.prototype.clearWindows = function (i, service) {
  1377. var packetData = this.current708Packet.data;
  1378. var b = packetData[++i];
  1379. var pts = this.getPts(i);
  1380. this.flushDisplayed(pts, service);
  1381. for (var winId = 0; winId < 8; winId++) {
  1382. if (b & 0x01 << winId) {
  1383. service.windows[winId].clearText();
  1384. }
  1385. }
  1386. return i;
  1387. };
  1388. /**
  1389. * Parse and execute the DLW command.
  1390. *
  1391. * Re-initialize windows based on the parsed bitmask.
  1392. *
  1393. * @param {Integer} i Current index in the 708 packet
  1394. * @param {Service} service The service object to be affected
  1395. * @return {Integer} New index after parsing
  1396. */
  1397. Cea708Stream.prototype.deleteWindows = function (i, service) {
  1398. var packetData = this.current708Packet.data;
  1399. var b = packetData[++i];
  1400. var pts = this.getPts(i);
  1401. this.flushDisplayed(pts, service);
  1402. for (var winId = 0; winId < 8; winId++) {
  1403. if (b & 0x01 << winId) {
  1404. service.windows[winId].reset();
  1405. }
  1406. }
  1407. return i;
  1408. };
  1409. /**
  1410. * Parse and execute the SPA command.
  1411. *
  1412. * Set pen attributes of the current window.
  1413. *
  1414. * @param {Integer} i Current index in the 708 packet
  1415. * @param {Service} service The service object to be affected
  1416. * @return {Integer} New index after parsing
  1417. */
  1418. Cea708Stream.prototype.setPenAttributes = function (i, service) {
  1419. var packetData = this.current708Packet.data;
  1420. var b = packetData[i];
  1421. var penAttr = service.currentWindow.penAttr;
  1422. b = packetData[++i];
  1423. penAttr.textTag = (b & 0xf0) >> 4; // tt
  1424. penAttr.offset = (b & 0x0c) >> 2; // o
  1425. penAttr.penSize = b & 0x03; // s
  1426. b = packetData[++i];
  1427. penAttr.italics = (b & 0x80) >> 7; // i
  1428. penAttr.underline = (b & 0x40) >> 6; // u
  1429. penAttr.edgeType = (b & 0x38) >> 3; // et
  1430. penAttr.fontStyle = b & 0x07; // fs
  1431. return i;
  1432. };
  1433. /**
  1434. * Parse and execute the SPC command.
  1435. *
  1436. * Set pen color of the current window.
  1437. *
  1438. * @param {Integer} i Current index in the 708 packet
  1439. * @param {Service} service The service object to be affected
  1440. * @return {Integer} New index after parsing
  1441. */
  1442. Cea708Stream.prototype.setPenColor = function (i, service) {
  1443. var packetData = this.current708Packet.data;
  1444. var b = packetData[i];
  1445. var penColor = service.currentWindow.penColor;
  1446. b = packetData[++i];
  1447. penColor.fgOpacity = (b & 0xc0) >> 6; // fo
  1448. penColor.fgRed = (b & 0x30) >> 4; // fr
  1449. penColor.fgGreen = (b & 0x0c) >> 2; // fg
  1450. penColor.fgBlue = b & 0x03; // fb
  1451. b = packetData[++i];
  1452. penColor.bgOpacity = (b & 0xc0) >> 6; // bo
  1453. penColor.bgRed = (b & 0x30) >> 4; // br
  1454. penColor.bgGreen = (b & 0x0c) >> 2; // bg
  1455. penColor.bgBlue = b & 0x03; // bb
  1456. b = packetData[++i];
  1457. penColor.edgeRed = (b & 0x30) >> 4; // er
  1458. penColor.edgeGreen = (b & 0x0c) >> 2; // eg
  1459. penColor.edgeBlue = b & 0x03; // eb
  1460. return i;
  1461. };
  1462. /**
  1463. * Parse and execute the SPL command.
  1464. *
  1465. * Set pen location of the current window.
  1466. *
  1467. * @param {Integer} i Current index in the 708 packet
  1468. * @param {Service} service The service object to be affected
  1469. * @return {Integer} New index after parsing
  1470. */
  1471. Cea708Stream.prototype.setPenLocation = function (i, service) {
  1472. var packetData = this.current708Packet.data;
  1473. var b = packetData[i];
  1474. var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
  1475. service.currentWindow.pendingNewLine = true;
  1476. b = packetData[++i];
  1477. penLoc.row = b & 0x0f; // r
  1478. b = packetData[++i];
  1479. penLoc.column = b & 0x3f; // c
  1480. return i;
  1481. };
  1482. /**
  1483. * Execute the RST command.
  1484. *
  1485. * Reset service to a clean slate. Re-initialize.
  1486. *
  1487. * @param {Integer} i Current index in the 708 packet
  1488. * @param {Service} service The service object to be affected
  1489. * @return {Service} Re-initialized service
  1490. */
  1491. Cea708Stream.prototype.reset = function (i, service) {
  1492. var pts = this.getPts(i);
  1493. this.flushDisplayed(pts, service);
  1494. return this.initService(service.serviceNum, i);
  1495. }; // This hash maps non-ASCII, special, and extended character codes to their
  1496. // proper Unicode equivalent. The first keys that are only a single byte
  1497. // are the non-standard ASCII characters, which simply map the CEA608 byte
  1498. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  1499. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  1500. // can be performed regardless of the field and data channel on which the
  1501. // character code was received.
  1502. var CHARACTER_TRANSLATION = {
  1503. 0x2a: 0xe1,
  1504. // á
  1505. 0x5c: 0xe9,
  1506. // é
  1507. 0x5e: 0xed,
  1508. // í
  1509. 0x5f: 0xf3,
  1510. // ó
  1511. 0x60: 0xfa,
  1512. // ú
  1513. 0x7b: 0xe7,
  1514. // ç
  1515. 0x7c: 0xf7,
  1516. // ÷
  1517. 0x7d: 0xd1,
  1518. // Ñ
  1519. 0x7e: 0xf1,
  1520. // ñ
  1521. 0x7f: 0x2588,
  1522. // █
  1523. 0x0130: 0xae,
  1524. // ®
  1525. 0x0131: 0xb0,
  1526. // °
  1527. 0x0132: 0xbd,
  1528. // ½
  1529. 0x0133: 0xbf,
  1530. // ¿
  1531. 0x0134: 0x2122,
  1532. // ™
  1533. 0x0135: 0xa2,
  1534. // ¢
  1535. 0x0136: 0xa3,
  1536. // £
  1537. 0x0137: 0x266a,
  1538. // ♪
  1539. 0x0138: 0xe0,
  1540. // à
  1541. 0x0139: 0xa0,
  1542. //
  1543. 0x013a: 0xe8,
  1544. // è
  1545. 0x013b: 0xe2,
  1546. // â
  1547. 0x013c: 0xea,
  1548. // ê
  1549. 0x013d: 0xee,
  1550. // î
  1551. 0x013e: 0xf4,
  1552. // ô
  1553. 0x013f: 0xfb,
  1554. // û
  1555. 0x0220: 0xc1,
  1556. // Á
  1557. 0x0221: 0xc9,
  1558. // É
  1559. 0x0222: 0xd3,
  1560. // Ó
  1561. 0x0223: 0xda,
  1562. // Ú
  1563. 0x0224: 0xdc,
  1564. // Ü
  1565. 0x0225: 0xfc,
  1566. // ü
  1567. 0x0226: 0x2018,
  1568. // ‘
  1569. 0x0227: 0xa1,
  1570. // ¡
  1571. 0x0228: 0x2a,
  1572. // *
  1573. 0x0229: 0x27,
  1574. // '
  1575. 0x022a: 0x2014,
  1576. // —
  1577. 0x022b: 0xa9,
  1578. // ©
  1579. 0x022c: 0x2120,
  1580. // ℠
  1581. 0x022d: 0x2022,
  1582. // •
  1583. 0x022e: 0x201c,
  1584. // “
  1585. 0x022f: 0x201d,
  1586. // ”
  1587. 0x0230: 0xc0,
  1588. // À
  1589. 0x0231: 0xc2,
  1590. // Â
  1591. 0x0232: 0xc7,
  1592. // Ç
  1593. 0x0233: 0xc8,
  1594. // È
  1595. 0x0234: 0xca,
  1596. // Ê
  1597. 0x0235: 0xcb,
  1598. // Ë
  1599. 0x0236: 0xeb,
  1600. // ë
  1601. 0x0237: 0xce,
  1602. // Î
  1603. 0x0238: 0xcf,
  1604. // Ï
  1605. 0x0239: 0xef,
  1606. // ï
  1607. 0x023a: 0xd4,
  1608. // Ô
  1609. 0x023b: 0xd9,
  1610. // Ù
  1611. 0x023c: 0xf9,
  1612. // ù
  1613. 0x023d: 0xdb,
  1614. // Û
  1615. 0x023e: 0xab,
  1616. // «
  1617. 0x023f: 0xbb,
  1618. // »
  1619. 0x0320: 0xc3,
  1620. // Ã
  1621. 0x0321: 0xe3,
  1622. // ã
  1623. 0x0322: 0xcd,
  1624. // Í
  1625. 0x0323: 0xcc,
  1626. // Ì
  1627. 0x0324: 0xec,
  1628. // ì
  1629. 0x0325: 0xd2,
  1630. // Ò
  1631. 0x0326: 0xf2,
  1632. // ò
  1633. 0x0327: 0xd5,
  1634. // Õ
  1635. 0x0328: 0xf5,
  1636. // õ
  1637. 0x0329: 0x7b,
  1638. // {
  1639. 0x032a: 0x7d,
  1640. // }
  1641. 0x032b: 0x5c,
  1642. // \
  1643. 0x032c: 0x5e,
  1644. // ^
  1645. 0x032d: 0x5f,
  1646. // _
  1647. 0x032e: 0x7c,
  1648. // |
  1649. 0x032f: 0x7e,
  1650. // ~
  1651. 0x0330: 0xc4,
  1652. // Ä
  1653. 0x0331: 0xe4,
  1654. // ä
  1655. 0x0332: 0xd6,
  1656. // Ö
  1657. 0x0333: 0xf6,
  1658. // ö
  1659. 0x0334: 0xdf,
  1660. // ß
  1661. 0x0335: 0xa5,
  1662. // ¥
  1663. 0x0336: 0xa4,
  1664. // ¤
  1665. 0x0337: 0x2502,
  1666. // │
  1667. 0x0338: 0xc5,
  1668. // Å
  1669. 0x0339: 0xe5,
  1670. // å
  1671. 0x033a: 0xd8,
  1672. // Ø
  1673. 0x033b: 0xf8,
  1674. // ø
  1675. 0x033c: 0x250c,
  1676. // ┌
  1677. 0x033d: 0x2510,
  1678. // ┐
  1679. 0x033e: 0x2514,
  1680. // └
  1681. 0x033f: 0x2518 // ┘
  1682. };
  1683. var getCharFromCode = function getCharFromCode(code) {
  1684. if (code === null) {
  1685. return '';
  1686. }
  1687. code = CHARACTER_TRANSLATION[code] || code;
  1688. return String.fromCharCode(code);
  1689. }; // the index of the last row in a CEA-608 display buffer
  1690. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  1691. // getting it through bit logic.
  1692. var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420]; // CEA-608 captions are rendered onto a 34x15 matrix of character
  1693. // cells. The "bottom" row is the last element in the outer array.
  1694. var createDisplayBuffer = function createDisplayBuffer() {
  1695. var result = [],
  1696. i = BOTTOM_ROW + 1;
  1697. while (i--) {
  1698. result.push('');
  1699. }
  1700. return result;
  1701. };
  1702. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  1703. Cea608Stream.prototype.init.call(this);
  1704. this.field_ = field || 0;
  1705. this.dataChannel_ = dataChannel || 0;
  1706. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  1707. this.setConstants();
  1708. this.reset();
  1709. this.push = function (packet) {
  1710. var data, swap, char0, char1, text; // remove the parity bits
  1711. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  1712. if (data === this.lastControlCode_) {
  1713. this.lastControlCode_ = null;
  1714. return;
  1715. } // Store control codes
  1716. if ((data & 0xf000) === 0x1000) {
  1717. this.lastControlCode_ = data;
  1718. } else if (data !== this.PADDING_) {
  1719. this.lastControlCode_ = null;
  1720. }
  1721. char0 = data >>> 8;
  1722. char1 = data & 0xff;
  1723. if (data === this.PADDING_) {
  1724. return;
  1725. } else if (data === this.RESUME_CAPTION_LOADING_) {
  1726. this.mode_ = 'popOn';
  1727. } else if (data === this.END_OF_CAPTION_) {
  1728. // If an EOC is received while in paint-on mode, the displayed caption
  1729. // text should be swapped to non-displayed memory as if it was a pop-on
  1730. // caption. Because of that, we should explicitly switch back to pop-on
  1731. // mode
  1732. this.mode_ = 'popOn';
  1733. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  1734. this.flushDisplayed(packet.pts); // flip memory
  1735. swap = this.displayed_;
  1736. this.displayed_ = this.nonDisplayed_;
  1737. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  1738. this.startPts_ = packet.pts;
  1739. } else if (data === this.ROLL_UP_2_ROWS_) {
  1740. this.rollUpRows_ = 2;
  1741. this.setRollUp(packet.pts);
  1742. } else if (data === this.ROLL_UP_3_ROWS_) {
  1743. this.rollUpRows_ = 3;
  1744. this.setRollUp(packet.pts);
  1745. } else if (data === this.ROLL_UP_4_ROWS_) {
  1746. this.rollUpRows_ = 4;
  1747. this.setRollUp(packet.pts);
  1748. } else if (data === this.CARRIAGE_RETURN_) {
  1749. this.clearFormatting(packet.pts);
  1750. this.flushDisplayed(packet.pts);
  1751. this.shiftRowsUp_();
  1752. this.startPts_ = packet.pts;
  1753. } else if (data === this.BACKSPACE_) {
  1754. if (this.mode_ === 'popOn') {
  1755. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  1756. } else {
  1757. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  1758. }
  1759. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  1760. this.flushDisplayed(packet.pts);
  1761. this.displayed_ = createDisplayBuffer();
  1762. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  1763. this.nonDisplayed_ = createDisplayBuffer();
  1764. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  1765. if (this.mode_ !== 'paintOn') {
  1766. // NOTE: This should be removed when proper caption positioning is
  1767. // implemented
  1768. this.flushDisplayed(packet.pts);
  1769. this.displayed_ = createDisplayBuffer();
  1770. }
  1771. this.mode_ = 'paintOn';
  1772. this.startPts_ = packet.pts; // Append special characters to caption text
  1773. } else if (this.isSpecialCharacter(char0, char1)) {
  1774. // Bitmask char0 so that we can apply character transformations
  1775. // regardless of field and data channel.
  1776. // Then byte-shift to the left and OR with char1 so we can pass the
  1777. // entire character code to `getCharFromCode`.
  1778. char0 = (char0 & 0x03) << 8;
  1779. text = getCharFromCode(char0 | char1);
  1780. this[this.mode_](packet.pts, text);
  1781. this.column_++; // Append extended characters to caption text
  1782. } else if (this.isExtCharacter(char0, char1)) {
  1783. // Extended characters always follow their "non-extended" equivalents.
  1784. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  1785. // decoders are supposed to drop the "è", while compliant decoders
  1786. // backspace the "e" and insert "è".
  1787. // Delete the previous character
  1788. if (this.mode_ === 'popOn') {
  1789. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  1790. } else {
  1791. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  1792. } // Bitmask char0 so that we can apply character transformations
  1793. // regardless of field and data channel.
  1794. // Then byte-shift to the left and OR with char1 so we can pass the
  1795. // entire character code to `getCharFromCode`.
  1796. char0 = (char0 & 0x03) << 8;
  1797. text = getCharFromCode(char0 | char1);
  1798. this[this.mode_](packet.pts, text);
  1799. this.column_++; // Process mid-row codes
  1800. } else if (this.isMidRowCode(char0, char1)) {
  1801. // Attributes are not additive, so clear all formatting
  1802. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  1803. // should be replaced with spaces, so add one now
  1804. this[this.mode_](packet.pts, ' ');
  1805. this.column_++;
  1806. if ((char1 & 0xe) === 0xe) {
  1807. this.addFormatting(packet.pts, ['i']);
  1808. }
  1809. if ((char1 & 0x1) === 0x1) {
  1810. this.addFormatting(packet.pts, ['u']);
  1811. } // Detect offset control codes and adjust cursor
  1812. } else if (this.isOffsetControlCode(char0, char1)) {
  1813. // Cursor position is set by indent PAC (see below) in 4-column
  1814. // increments, with an additional offset code of 1-3 to reach any
  1815. // of the 32 columns specified by CEA-608. So all we need to do
  1816. // here is increment the column cursor by the given offset.
  1817. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  1818. } else if (this.isPAC(char0, char1)) {
  1819. // There's no logic for PAC -> row mapping, so we have to just
  1820. // find the row code in an array and use its index :(
  1821. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  1822. if (this.mode_ === 'rollUp') {
  1823. // This implies that the base row is incorrectly set.
  1824. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  1825. // of roll-up rows set.
  1826. if (row - this.rollUpRows_ + 1 < 0) {
  1827. row = this.rollUpRows_ - 1;
  1828. }
  1829. this.setRollUp(packet.pts, row);
  1830. }
  1831. if (row !== this.row_) {
  1832. // formatting is only persistent for current row
  1833. this.clearFormatting(packet.pts);
  1834. this.row_ = row;
  1835. } // All PACs can apply underline, so detect and apply
  1836. // (All odd-numbered second bytes set underline)
  1837. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  1838. this.addFormatting(packet.pts, ['u']);
  1839. }
  1840. if ((data & 0x10) === 0x10) {
  1841. // We've got an indent level code. Each successive even number
  1842. // increments the column cursor by 4, so we can get the desired
  1843. // column position by bit-shifting to the right (to get n/2)
  1844. // and multiplying by 4.
  1845. this.column_ = ((data & 0xe) >> 1) * 4;
  1846. }
  1847. if (this.isColorPAC(char1)) {
  1848. // it's a color code, though we only support white, which
  1849. // can be either normal or italicized. white italics can be
  1850. // either 0x4e or 0x6e depending on the row, so we just
  1851. // bitwise-and with 0xe to see if italics should be turned on
  1852. if ((char1 & 0xe) === 0xe) {
  1853. this.addFormatting(packet.pts, ['i']);
  1854. }
  1855. } // We have a normal character in char0, and possibly one in char1
  1856. } else if (this.isNormalChar(char0)) {
  1857. if (char1 === 0x00) {
  1858. char1 = null;
  1859. }
  1860. text = getCharFromCode(char0);
  1861. text += getCharFromCode(char1);
  1862. this[this.mode_](packet.pts, text);
  1863. this.column_ += text.length;
  1864. } // finish data processing
  1865. };
  1866. };
  1867. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  1868. // display buffer
  1869. Cea608Stream.prototype.flushDisplayed = function (pts) {
  1870. var content = this.displayed_ // remove spaces from the start and end of the string
  1871. .map(function (row, index) {
  1872. try {
  1873. return row.trim();
  1874. } catch (e) {
  1875. // Ordinarily, this shouldn't happen. However, caption
  1876. // parsing errors should not throw exceptions and
  1877. // break playback.
  1878. this.trigger('log', {
  1879. level: 'warn',
  1880. message: 'Skipping a malformed 608 caption at index ' + index + '.'
  1881. });
  1882. return '';
  1883. }
  1884. }, this) // combine all text rows to display in one cue
  1885. .join('\n') // and remove blank rows from the start and end, but not the middle
  1886. .replace(/^\n+|\n+$/g, '');
  1887. if (content.length) {
  1888. this.trigger('data', {
  1889. startPts: this.startPts_,
  1890. endPts: pts,
  1891. text: content,
  1892. stream: this.name_
  1893. });
  1894. }
  1895. };
  1896. /**
  1897. * Zero out the data, used for startup and on seek
  1898. */
  1899. Cea608Stream.prototype.reset = function () {
  1900. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  1901. // actually display captions. If a caption is shifted to a row
  1902. // with a lower index than this, it is cleared from the display
  1903. // buffer
  1904. this.topRow_ = 0;
  1905. this.startPts_ = 0;
  1906. this.displayed_ = createDisplayBuffer();
  1907. this.nonDisplayed_ = createDisplayBuffer();
  1908. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  1909. this.column_ = 0;
  1910. this.row_ = BOTTOM_ROW;
  1911. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  1912. this.formatting_ = [];
  1913. };
  1914. /**
  1915. * Sets up control code and related constants for this instance
  1916. */
  1917. Cea608Stream.prototype.setConstants = function () {
  1918. // The following attributes have these uses:
  1919. // ext_ : char0 for mid-row codes, and the base for extended
  1920. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  1921. // extended codes)
  1922. // control_: char0 for control codes, except byte-shifted to the
  1923. // left so that we can do this.control_ | CONTROL_CODE
  1924. // offset_: char0 for tab offset codes
  1925. //
  1926. // It's also worth noting that control codes, and _only_ control codes,
  1927. // differ between field 1 and field2. Field 2 control codes are always
  1928. // their field 1 value plus 1. That's why there's the "| field" on the
  1929. // control value.
  1930. if (this.dataChannel_ === 0) {
  1931. this.BASE_ = 0x10;
  1932. this.EXT_ = 0x11;
  1933. this.CONTROL_ = (0x14 | this.field_) << 8;
  1934. this.OFFSET_ = 0x17;
  1935. } else if (this.dataChannel_ === 1) {
  1936. this.BASE_ = 0x18;
  1937. this.EXT_ = 0x19;
  1938. this.CONTROL_ = (0x1c | this.field_) << 8;
  1939. this.OFFSET_ = 0x1f;
  1940. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  1941. // list is not exhaustive. For a more comprehensive listing and semantics see
  1942. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  1943. // Padding
  1944. this.PADDING_ = 0x0000; // Pop-on Mode
  1945. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  1946. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  1947. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  1948. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  1949. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  1950. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  1951. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  1952. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  1953. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  1954. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  1955. };
  1956. /**
  1957. * Detects if the 2-byte packet data is a special character
  1958. *
  1959. * Special characters have a second byte in the range 0x30 to 0x3f,
  1960. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  1961. * data channel 2).
  1962. *
  1963. * @param {Integer} char0 The first byte
  1964. * @param {Integer} char1 The second byte
  1965. * @return {Boolean} Whether the 2 bytes are an special character
  1966. */
  1967. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  1968. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  1969. };
  1970. /**
  1971. * Detects if the 2-byte packet data is an extended character
  1972. *
  1973. * Extended characters have a second byte in the range 0x20 to 0x3f,
  1974. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  1975. * 0x1a or 0x1b (for data channel 2).
  1976. *
  1977. * @param {Integer} char0 The first byte
  1978. * @param {Integer} char1 The second byte
  1979. * @return {Boolean} Whether the 2 bytes are an extended character
  1980. */
  1981. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  1982. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  1983. };
  1984. /**
  1985. * Detects if the 2-byte packet is a mid-row code
  1986. *
  1987. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  1988. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  1989. * channel 2).
  1990. *
  1991. * @param {Integer} char0 The first byte
  1992. * @param {Integer} char1 The second byte
  1993. * @return {Boolean} Whether the 2 bytes are a mid-row code
  1994. */
  1995. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  1996. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  1997. };
  1998. /**
  1999. * Detects if the 2-byte packet is an offset control code
  2000. *
  2001. * Offset control codes have a second byte in the range 0x21 to 0x23,
  2002. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  2003. * data channel 2).
  2004. *
  2005. * @param {Integer} char0 The first byte
  2006. * @param {Integer} char1 The second byte
  2007. * @return {Boolean} Whether the 2 bytes are an offset control code
  2008. */
  2009. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  2010. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  2011. };
  2012. /**
  2013. * Detects if the 2-byte packet is a Preamble Address Code
  2014. *
  2015. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  2016. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  2017. * range 0x40 to 0x7f.
  2018. *
  2019. * @param {Integer} char0 The first byte
  2020. * @param {Integer} char1 The second byte
  2021. * @return {Boolean} Whether the 2 bytes are a PAC
  2022. */
  2023. Cea608Stream.prototype.isPAC = function (char0, char1) {
  2024. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  2025. };
  2026. /**
  2027. * Detects if a packet's second byte is in the range of a PAC color code
  2028. *
  2029. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  2030. * 0x60 to 0x6f.
  2031. *
  2032. * @param {Integer} char1 The second byte
  2033. * @return {Boolean} Whether the byte is a color PAC
  2034. */
  2035. Cea608Stream.prototype.isColorPAC = function (char1) {
  2036. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  2037. };
  2038. /**
  2039. * Detects if a single byte is in the range of a normal character
  2040. *
  2041. * Normal text bytes are in the range 0x20 to 0x7f.
  2042. *
  2043. * @param {Integer} char The byte
  2044. * @return {Boolean} Whether the byte is a normal character
  2045. */
  2046. Cea608Stream.prototype.isNormalChar = function (char) {
  2047. return char >= 0x20 && char <= 0x7f;
  2048. };
  2049. /**
  2050. * Configures roll-up
  2051. *
  2052. * @param {Integer} pts Current PTS
  2053. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  2054. * a new position
  2055. */
  2056. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  2057. // Reset the base row to the bottom row when switching modes
  2058. if (this.mode_ !== 'rollUp') {
  2059. this.row_ = BOTTOM_ROW;
  2060. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  2061. this.flushDisplayed(pts);
  2062. this.nonDisplayed_ = createDisplayBuffer();
  2063. this.displayed_ = createDisplayBuffer();
  2064. }
  2065. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  2066. // move currently displayed captions (up or down) to the new base row
  2067. for (var i = 0; i < this.rollUpRows_; i++) {
  2068. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  2069. this.displayed_[this.row_ - i] = '';
  2070. }
  2071. }
  2072. if (newBaseRow === undefined) {
  2073. newBaseRow = this.row_;
  2074. }
  2075. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  2076. }; // Adds the opening HTML tag for the passed character to the caption text,
  2077. // and keeps track of it for later closing
  2078. Cea608Stream.prototype.addFormatting = function (pts, format) {
  2079. this.formatting_ = this.formatting_.concat(format);
  2080. var text = format.reduce(function (text, format) {
  2081. return text + '<' + format + '>';
  2082. }, '');
  2083. this[this.mode_](pts, text);
  2084. }; // Adds HTML closing tags for current formatting to caption text and
  2085. // clears remembered formatting
  2086. Cea608Stream.prototype.clearFormatting = function (pts) {
  2087. if (!this.formatting_.length) {
  2088. return;
  2089. }
  2090. var text = this.formatting_.reverse().reduce(function (text, format) {
  2091. return text + '</' + format + '>';
  2092. }, '');
  2093. this.formatting_ = [];
  2094. this[this.mode_](pts, text);
  2095. }; // Mode Implementations
  2096. Cea608Stream.prototype.popOn = function (pts, text) {
  2097. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  2098. baseRow += text;
  2099. this.nonDisplayed_[this.row_] = baseRow;
  2100. };
  2101. Cea608Stream.prototype.rollUp = function (pts, text) {
  2102. var baseRow = this.displayed_[this.row_];
  2103. baseRow += text;
  2104. this.displayed_[this.row_] = baseRow;
  2105. };
  2106. Cea608Stream.prototype.shiftRowsUp_ = function () {
  2107. var i; // clear out inactive rows
  2108. for (i = 0; i < this.topRow_; i++) {
  2109. this.displayed_[i] = '';
  2110. }
  2111. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  2112. this.displayed_[i] = '';
  2113. } // shift displayed rows up
  2114. for (i = this.topRow_; i < this.row_; i++) {
  2115. this.displayed_[i] = this.displayed_[i + 1];
  2116. } // clear out the bottom row
  2117. this.displayed_[this.row_] = '';
  2118. };
  2119. Cea608Stream.prototype.paintOn = function (pts, text) {
  2120. var baseRow = this.displayed_[this.row_];
  2121. baseRow += text;
  2122. this.displayed_[this.row_] = baseRow;
  2123. }; // exports
  2124. var captionStream = {
  2125. CaptionStream: CaptionStream,
  2126. Cea608Stream: Cea608Stream,
  2127. Cea708Stream: Cea708Stream
  2128. };
  2129. /**
  2130. * mux.js
  2131. *
  2132. * Copyright (c) Brightcove
  2133. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  2134. */
  2135. var streamTypes = {
  2136. H264_STREAM_TYPE: 0x1B,
  2137. ADTS_STREAM_TYPE: 0x0F,
  2138. METADATA_STREAM_TYPE: 0x15
  2139. };
  2140. var MAX_TS = 8589934592;
  2141. var RO_THRESH = 4294967296;
  2142. var TYPE_SHARED = 'shared';
  2143. var handleRollover = function handleRollover(value, reference) {
  2144. var direction = 1;
  2145. if (value > reference) {
  2146. // If the current timestamp value is greater than our reference timestamp and we detect a
  2147. // timestamp rollover, this means the roll over is happening in the opposite direction.
  2148. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  2149. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  2150. // rollover point. In loading this segment, the timestamp values will be very large,
  2151. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  2152. // the time stamp to be `value - 2^33`.
  2153. direction = -1;
  2154. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  2155. // cause an incorrect adjustment.
  2156. while (Math.abs(reference - value) > RO_THRESH) {
  2157. value += direction * MAX_TS;
  2158. }
  2159. return value;
  2160. };
  2161. var TimestampRolloverStream$1 = function TimestampRolloverStream(type) {
  2162. var lastDTS, referenceDTS;
  2163. TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
  2164. // video and audio. We could use `undefined` here, but having a string
  2165. // makes debugging a little clearer.
  2166. this.type_ = type || TYPE_SHARED;
  2167. this.push = function (data) {
  2168. // Any "shared" rollover streams will accept _all_ data. Otherwise,
  2169. // streams will only accept data that matches their type.
  2170. if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
  2171. return;
  2172. }
  2173. if (referenceDTS === undefined) {
  2174. referenceDTS = data.dts;
  2175. }
  2176. data.dts = handleRollover(data.dts, referenceDTS);
  2177. data.pts = handleRollover(data.pts, referenceDTS);
  2178. lastDTS = data.dts;
  2179. this.trigger('data', data);
  2180. };
  2181. this.flush = function () {
  2182. referenceDTS = lastDTS;
  2183. this.trigger('done');
  2184. };
  2185. this.endTimeline = function () {
  2186. this.flush();
  2187. this.trigger('endedtimeline');
  2188. };
  2189. this.discontinuity = function () {
  2190. referenceDTS = void 0;
  2191. lastDTS = void 0;
  2192. };
  2193. this.reset = function () {
  2194. this.discontinuity();
  2195. this.trigger('reset');
  2196. };
  2197. };
  2198. TimestampRolloverStream$1.prototype = new stream();
  2199. var timestampRolloverStream = {
  2200. TimestampRolloverStream: TimestampRolloverStream$1,
  2201. handleRollover: handleRollover
  2202. };
  2203. // IE11 doesn't support indexOf for TypedArrays.
  2204. // Once IE11 support is dropped, this function should be removed.
  2205. var typedArrayIndexOf$1 = function typedArrayIndexOf(typedArray, element, fromIndex) {
  2206. if (!typedArray) {
  2207. return -1;
  2208. }
  2209. var currentIndex = fromIndex;
  2210. for (; currentIndex < typedArray.length; currentIndex++) {
  2211. if (typedArray[currentIndex] === element) {
  2212. return currentIndex;
  2213. }
  2214. }
  2215. return -1;
  2216. };
  2217. var typedArray = {
  2218. typedArrayIndexOf: typedArrayIndexOf$1
  2219. };
  2220. var typedArrayIndexOf = typedArray.typedArrayIndexOf,
  2221. // Frames that allow different types of text encoding contain a text
  2222. // encoding description byte [ID3v2.4.0 section 4.]
  2223. textEncodingDescriptionByte = {
  2224. Iso88591: 0x00,
  2225. // ISO-8859-1, terminated with \0.
  2226. Utf16: 0x01,
  2227. // UTF-16 encoded Unicode BOM, terminated with \0\0
  2228. Utf16be: 0x02,
  2229. // UTF-16BE encoded Unicode, without BOM, terminated with \0\0
  2230. Utf8: 0x03 // UTF-8 encoded Unicode, terminated with \0
  2231. },
  2232. // return a percent-encoded representation of the specified byte range
  2233. // @see http://en.wikipedia.org/wiki/Percent-encoding
  2234. percentEncode = function percentEncode(bytes, start, end) {
  2235. var i,
  2236. result = '';
  2237. for (i = start; i < end; i++) {
  2238. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  2239. }
  2240. return result;
  2241. },
  2242. // return the string representation of the specified byte range,
  2243. // interpreted as UTf-8.
  2244. parseUtf8 = function parseUtf8(bytes, start, end) {
  2245. return decodeURIComponent(percentEncode(bytes, start, end));
  2246. },
  2247. // return the string representation of the specified byte range,
  2248. // interpreted as ISO-8859-1.
  2249. parseIso88591 = function parseIso88591(bytes, start, end) {
  2250. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  2251. },
  2252. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  2253. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  2254. },
  2255. frameParsers = {
  2256. 'APIC': function APIC(frame) {
  2257. var i = 1,
  2258. mimeTypeEndIndex,
  2259. descriptionEndIndex,
  2260. LINK_MIME_TYPE = '-->';
  2261. if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
  2262. // ignore frames with unrecognized character encodings
  2263. return;
  2264. } // parsing fields [ID3v2.4.0 section 4.14.]
  2265. mimeTypeEndIndex = typedArrayIndexOf(frame.data, 0, i);
  2266. if (mimeTypeEndIndex < 0) {
  2267. // malformed frame
  2268. return;
  2269. } // parsing Mime type field (terminated with \0)
  2270. frame.mimeType = parseIso88591(frame.data, i, mimeTypeEndIndex);
  2271. i = mimeTypeEndIndex + 1; // parsing 1-byte Picture Type field
  2272. frame.pictureType = frame.data[i];
  2273. i++;
  2274. descriptionEndIndex = typedArrayIndexOf(frame.data, 0, i);
  2275. if (descriptionEndIndex < 0) {
  2276. // malformed frame
  2277. return;
  2278. } // parsing Description field (terminated with \0)
  2279. frame.description = parseUtf8(frame.data, i, descriptionEndIndex);
  2280. i = descriptionEndIndex + 1;
  2281. if (frame.mimeType === LINK_MIME_TYPE) {
  2282. // parsing Picture Data field as URL (always represented as ISO-8859-1 [ID3v2.4.0 section 4.])
  2283. frame.url = parseIso88591(frame.data, i, frame.data.length);
  2284. } else {
  2285. // parsing Picture Data field as binary data
  2286. frame.pictureData = frame.data.subarray(i, frame.data.length);
  2287. }
  2288. },
  2289. 'T*': function T(frame) {
  2290. if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
  2291. // ignore frames with unrecognized character encodings
  2292. return;
  2293. } // parse text field, do not include null terminator in the frame value
  2294. // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
  2295. frame.value = parseUtf8(frame.data, 1, frame.data.length).replace(/\0*$/, ''); // text information frames supports multiple strings, stored as a terminator separated list [ID3v2.4.0 section 4.2.]
  2296. frame.values = frame.value.split('\0');
  2297. },
  2298. 'TXXX': function TXXX(frame) {
  2299. var descriptionEndIndex;
  2300. if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
  2301. // ignore frames with unrecognized character encodings
  2302. return;
  2303. }
  2304. descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
  2305. if (descriptionEndIndex === -1) {
  2306. return;
  2307. } // parse the text fields
  2308. frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // do not include the null terminator in the tag value
  2309. // frames that allow different types of encoding contain terminated text
  2310. // [ID3v2.4.0 section 4.]
  2311. frame.value = parseUtf8(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0*$/, '');
  2312. frame.data = frame.value;
  2313. },
  2314. 'W*': function W(frame) {
  2315. // parse URL field; URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
  2316. // if the value is followed by a string termination all the following information should be ignored [ID3v2.4.0 section 4.3]
  2317. frame.url = parseIso88591(frame.data, 0, frame.data.length).replace(/\0.*$/, '');
  2318. },
  2319. 'WXXX': function WXXX(frame) {
  2320. var descriptionEndIndex;
  2321. if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
  2322. // ignore frames with unrecognized character encodings
  2323. return;
  2324. }
  2325. descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
  2326. if (descriptionEndIndex === -1) {
  2327. return;
  2328. } // parse the description and URL fields
  2329. frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
  2330. // if the value is followed by a string termination all the following information
  2331. // should be ignored [ID3v2.4.0 section 4.3]
  2332. frame.url = parseIso88591(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0.*$/, '');
  2333. },
  2334. 'PRIV': function PRIV(frame) {
  2335. var i;
  2336. for (i = 0; i < frame.data.length; i++) {
  2337. if (frame.data[i] === 0) {
  2338. // parse the description and URL fields
  2339. frame.owner = parseIso88591(frame.data, 0, i);
  2340. break;
  2341. }
  2342. }
  2343. frame.privateData = frame.data.subarray(i + 1);
  2344. frame.data = frame.privateData;
  2345. }
  2346. };
  2347. var parseId3Frames = function parseId3Frames(data) {
  2348. var frameSize,
  2349. frameHeader,
  2350. frameStart = 10,
  2351. tagSize = 0,
  2352. frames = []; // If we don't have enough data for a header, 10 bytes,
  2353. // or 'ID3' in the first 3 bytes this is not a valid ID3 tag.
  2354. if (data.length < 10 || data[0] !== 'I'.charCodeAt(0) || data[1] !== 'D'.charCodeAt(0) || data[2] !== '3'.charCodeAt(0)) {
  2355. return;
  2356. } // the frame size is transmitted as a 28-bit integer in the
  2357. // last four bytes of the ID3 header.
  2358. // The most significant bit of each byte is dropped and the
  2359. // results concatenated to recover the actual value.
  2360. tagSize = parseSyncSafeInteger(data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  2361. // convenient for our comparisons to include it
  2362. tagSize += 10; // check bit 6 of byte 5 for the extended header flag.
  2363. var hasExtendedHeader = data[5] & 0x40;
  2364. if (hasExtendedHeader) {
  2365. // advance the frame start past the extended header
  2366. frameStart += 4; // header size field
  2367. frameStart += parseSyncSafeInteger(data.subarray(10, 14));
  2368. tagSize -= parseSyncSafeInteger(data.subarray(16, 20)); // clip any padding off the end
  2369. } // parse one or more ID3 frames
  2370. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  2371. do {
  2372. // determine the number of bytes in this frame
  2373. frameSize = parseSyncSafeInteger(data.subarray(frameStart + 4, frameStart + 8));
  2374. if (frameSize < 1) {
  2375. break;
  2376. }
  2377. frameHeader = String.fromCharCode(data[frameStart], data[frameStart + 1], data[frameStart + 2], data[frameStart + 3]);
  2378. var frame = {
  2379. id: frameHeader,
  2380. data: data.subarray(frameStart + 10, frameStart + frameSize + 10)
  2381. };
  2382. frame.key = frame.id; // parse frame values
  2383. if (frameParsers[frame.id]) {
  2384. // use frame specific parser
  2385. frameParsers[frame.id](frame);
  2386. } else if (frame.id[0] === 'T') {
  2387. // use text frame generic parser
  2388. frameParsers['T*'](frame);
  2389. } else if (frame.id[0] === 'W') {
  2390. // use URL link frame generic parser
  2391. frameParsers['W*'](frame);
  2392. }
  2393. frames.push(frame);
  2394. frameStart += 10; // advance past the frame header
  2395. frameStart += frameSize; // advance past the frame body
  2396. } while (frameStart < tagSize);
  2397. return frames;
  2398. };
  2399. var parseId3 = {
  2400. parseId3Frames: parseId3Frames,
  2401. parseSyncSafeInteger: parseSyncSafeInteger,
  2402. frameParsers: frameParsers
  2403. };
  2404. var _MetadataStream;
  2405. _MetadataStream = function MetadataStream(options) {
  2406. var settings = {
  2407. // the bytes of the program-level descriptor field in MP2T
  2408. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  2409. // program element descriptors"
  2410. descriptor: options && options.descriptor
  2411. },
  2412. // the total size in bytes of the ID3 tag being parsed
  2413. tagSize = 0,
  2414. // tag data that is not complete enough to be parsed
  2415. buffer = [],
  2416. // the total number of bytes currently in the buffer
  2417. bufferSize = 0,
  2418. i;
  2419. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  2420. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  2421. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  2422. if (settings.descriptor) {
  2423. for (i = 0; i < settings.descriptor.length; i++) {
  2424. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  2425. }
  2426. }
  2427. this.push = function (chunk) {
  2428. var tag, frameStart, frameSize, frame, i, frameHeader;
  2429. if (chunk.type !== 'timed-metadata') {
  2430. return;
  2431. } // if data_alignment_indicator is set in the PES header,
  2432. // we must have the start of a new ID3 tag. Assume anything
  2433. // remaining in the buffer was malformed and throw it out
  2434. if (chunk.dataAlignmentIndicator) {
  2435. bufferSize = 0;
  2436. buffer.length = 0;
  2437. } // ignore events that don't look like ID3 data
  2438. 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))) {
  2439. this.trigger('log', {
  2440. level: 'warn',
  2441. message: 'Skipping unrecognized metadata packet'
  2442. });
  2443. return;
  2444. } // add this chunk to the data we've collected so far
  2445. buffer.push(chunk);
  2446. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  2447. if (buffer.length === 1) {
  2448. // the frame size is transmitted as a 28-bit integer in the
  2449. // last four bytes of the ID3 header.
  2450. // The most significant bit of each byte is dropped and the
  2451. // results concatenated to recover the actual value.
  2452. tagSize = parseId3.parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  2453. // convenient for our comparisons to include it
  2454. tagSize += 10;
  2455. } // if the entire frame has not arrived, wait for more data
  2456. if (bufferSize < tagSize) {
  2457. return;
  2458. } // collect the entire frame so it can be parsed
  2459. tag = {
  2460. data: new Uint8Array(tagSize),
  2461. frames: [],
  2462. pts: buffer[0].pts,
  2463. dts: buffer[0].dts
  2464. };
  2465. for (i = 0; i < tagSize;) {
  2466. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  2467. i += buffer[0].data.byteLength;
  2468. bufferSize -= buffer[0].data.byteLength;
  2469. buffer.shift();
  2470. } // find the start of the first frame and the end of the tag
  2471. frameStart = 10;
  2472. if (tag.data[5] & 0x40) {
  2473. // advance the frame start past the extended header
  2474. frameStart += 4; // header size field
  2475. frameStart += parseId3.parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  2476. tagSize -= parseId3.parseSyncSafeInteger(tag.data.subarray(16, 20));
  2477. } // parse one or more ID3 frames
  2478. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  2479. do {
  2480. // determine the number of bytes in this frame
  2481. frameSize = parseId3.parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  2482. if (frameSize < 1) {
  2483. this.trigger('log', {
  2484. level: 'warn',
  2485. message: 'Malformed ID3 frame encountered. Skipping remaining metadata parsing.'
  2486. }); // If the frame is malformed, don't parse any further frames but allow previous valid parsed frames
  2487. // to be sent along.
  2488. break;
  2489. }
  2490. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  2491. frame = {
  2492. id: frameHeader,
  2493. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  2494. };
  2495. frame.key = frame.id; // parse frame values
  2496. if (parseId3.frameParsers[frame.id]) {
  2497. // use frame specific parser
  2498. parseId3.frameParsers[frame.id](frame);
  2499. } else if (frame.id[0] === 'T') {
  2500. // use text frame generic parser
  2501. parseId3.frameParsers['T*'](frame);
  2502. } else if (frame.id[0] === 'W') {
  2503. // use URL link frame generic parser
  2504. parseId3.frameParsers['W*'](frame);
  2505. } // handle the special PRIV frame used to indicate the start
  2506. // time for raw AAC data
  2507. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  2508. var d = frame.data,
  2509. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  2510. size *= 4;
  2511. size += d[7] & 0x03;
  2512. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  2513. // on the value of this frame
  2514. // we couldn't have known the appropriate pts and dts before
  2515. // parsing this ID3 tag so set those values now
  2516. if (tag.pts === undefined && tag.dts === undefined) {
  2517. tag.pts = frame.timeStamp;
  2518. tag.dts = frame.timeStamp;
  2519. }
  2520. this.trigger('timestamp', frame);
  2521. }
  2522. tag.frames.push(frame);
  2523. frameStart += 10; // advance past the frame header
  2524. frameStart += frameSize; // advance past the frame body
  2525. } while (frameStart < tagSize);
  2526. this.trigger('data', tag);
  2527. };
  2528. };
  2529. _MetadataStream.prototype = new stream();
  2530. var metadataStream = _MetadataStream;
  2531. var TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
  2532. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  2533. var MP2T_PACKET_LENGTH = 188,
  2534. // bytes
  2535. SYNC_BYTE = 0x47;
  2536. /**
  2537. * Splits an incoming stream of binary data into MPEG-2 Transport
  2538. * Stream packets.
  2539. */
  2540. _TransportPacketStream = function TransportPacketStream() {
  2541. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  2542. bytesInBuffer = 0;
  2543. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  2544. /**
  2545. * Split a stream of data into M2TS packets
  2546. **/
  2547. this.push = function (bytes) {
  2548. var startIndex = 0,
  2549. endIndex = MP2T_PACKET_LENGTH,
  2550. everything; // If there are bytes remaining from the last segment, prepend them to the
  2551. // bytes that were pushed in
  2552. if (bytesInBuffer) {
  2553. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  2554. everything.set(buffer.subarray(0, bytesInBuffer));
  2555. everything.set(bytes, bytesInBuffer);
  2556. bytesInBuffer = 0;
  2557. } else {
  2558. everything = bytes;
  2559. } // While we have enough data for a packet
  2560. while (endIndex < everything.byteLength) {
  2561. // Look for a pair of start and end sync bytes in the data..
  2562. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  2563. // We found a packet so emit it and jump one whole packet forward in
  2564. // the stream
  2565. this.trigger('data', everything.subarray(startIndex, endIndex));
  2566. startIndex += MP2T_PACKET_LENGTH;
  2567. endIndex += MP2T_PACKET_LENGTH;
  2568. continue;
  2569. } // If we get here, we have somehow become de-synchronized and we need to step
  2570. // forward one byte at a time until we find a pair of sync bytes that denote
  2571. // a packet
  2572. startIndex++;
  2573. endIndex++;
  2574. } // If there was some data left over at the end of the segment that couldn't
  2575. // possibly be a whole packet, keep it because it might be the start of a packet
  2576. // that continues in the next segment
  2577. if (startIndex < everything.byteLength) {
  2578. buffer.set(everything.subarray(startIndex), 0);
  2579. bytesInBuffer = everything.byteLength - startIndex;
  2580. }
  2581. };
  2582. /**
  2583. * Passes identified M2TS packets to the TransportParseStream to be parsed
  2584. **/
  2585. this.flush = function () {
  2586. // If the buffer contains a whole packet when we are being flushed, emit it
  2587. // and empty the buffer. Otherwise hold onto the data because it may be
  2588. // important for decoding the next segment
  2589. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  2590. this.trigger('data', buffer);
  2591. bytesInBuffer = 0;
  2592. }
  2593. this.trigger('done');
  2594. };
  2595. this.endTimeline = function () {
  2596. this.flush();
  2597. this.trigger('endedtimeline');
  2598. };
  2599. this.reset = function () {
  2600. bytesInBuffer = 0;
  2601. this.trigger('reset');
  2602. };
  2603. };
  2604. _TransportPacketStream.prototype = new stream();
  2605. /**
  2606. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  2607. * forms of the individual transport stream packets.
  2608. */
  2609. _TransportParseStream = function TransportParseStream() {
  2610. var parsePsi, parsePat, parsePmt, self;
  2611. _TransportParseStream.prototype.init.call(this);
  2612. self = this;
  2613. this.packetsWaitingForPmt = [];
  2614. this.programMapTable = undefined;
  2615. parsePsi = function parsePsi(payload, psi) {
  2616. var offset = 0; // PSI packets may be split into multiple sections and those
  2617. // sections may be split into multiple packets. If a PSI
  2618. // section starts in this packet, the payload_unit_start_indicator
  2619. // will be true and the first byte of the payload will indicate
  2620. // the offset from the current position to the start of the
  2621. // section.
  2622. if (psi.payloadUnitStartIndicator) {
  2623. offset += payload[offset] + 1;
  2624. }
  2625. if (psi.type === 'pat') {
  2626. parsePat(payload.subarray(offset), psi);
  2627. } else {
  2628. parsePmt(payload.subarray(offset), psi);
  2629. }
  2630. };
  2631. parsePat = function parsePat(payload, pat) {
  2632. pat.section_number = payload[7]; // eslint-disable-line camelcase
  2633. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  2634. // skip the PSI header and parse the first PMT entry
  2635. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  2636. pat.pmtPid = self.pmtPid;
  2637. };
  2638. /**
  2639. * Parse out the relevant fields of a Program Map Table (PMT).
  2640. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  2641. * packet. The first byte in this array should be the table_id
  2642. * field.
  2643. * @param pmt {object} the object that should be decorated with
  2644. * fields parsed from the PMT.
  2645. */
  2646. parsePmt = function parsePmt(payload, pmt) {
  2647. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  2648. // take effect. We don't believe this should ever be the case
  2649. // for HLS but we'll ignore "forward" PMT declarations if we see
  2650. // them. Future PMT declarations have the current_next_indicator
  2651. // set to zero.
  2652. if (!(payload[5] & 0x01)) {
  2653. return;
  2654. } // overwrite any existing program map table
  2655. self.programMapTable = {
  2656. video: null,
  2657. audio: null,
  2658. 'timed-metadata': {}
  2659. }; // the mapping table ends at the end of the current section
  2660. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  2661. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  2662. // long the program info descriptors are
  2663. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  2664. offset = 12 + programInfoLength;
  2665. while (offset < tableEnd) {
  2666. var streamType = payload[offset];
  2667. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  2668. // TODO: should this be done for metadata too? for now maintain behavior of
  2669. // multiple metadata streams
  2670. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  2671. self.programMapTable.video = pid;
  2672. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  2673. self.programMapTable.audio = pid;
  2674. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  2675. // map pid to stream type for metadata streams
  2676. self.programMapTable['timed-metadata'][pid] = streamType;
  2677. } // move to the next table entry
  2678. // skip past the elementary stream descriptors, if present
  2679. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  2680. } // record the map on the packet as well
  2681. pmt.programMapTable = self.programMapTable;
  2682. };
  2683. /**
  2684. * Deliver a new MP2T packet to the next stream in the pipeline.
  2685. */
  2686. this.push = function (packet) {
  2687. var result = {},
  2688. offset = 4;
  2689. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  2690. result.pid = packet[1] & 0x1f;
  2691. result.pid <<= 8;
  2692. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  2693. // fifth byte of the TS packet header. The adaptation field is
  2694. // used to add stuffing to PES packets that don't fill a complete
  2695. // TS packet, and to specify some forms of timing and control data
  2696. // that we do not currently use.
  2697. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  2698. offset += packet[offset] + 1;
  2699. } // parse the rest of the packet based on the type
  2700. if (result.pid === 0) {
  2701. result.type = 'pat';
  2702. parsePsi(packet.subarray(offset), result);
  2703. this.trigger('data', result);
  2704. } else if (result.pid === this.pmtPid) {
  2705. result.type = 'pmt';
  2706. parsePsi(packet.subarray(offset), result);
  2707. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  2708. while (this.packetsWaitingForPmt.length) {
  2709. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  2710. }
  2711. } else if (this.programMapTable === undefined) {
  2712. // When we have not seen a PMT yet, defer further processing of
  2713. // PES packets until one has been parsed
  2714. this.packetsWaitingForPmt.push([packet, offset, result]);
  2715. } else {
  2716. this.processPes_(packet, offset, result);
  2717. }
  2718. };
  2719. this.processPes_ = function (packet, offset, result) {
  2720. // set the appropriate stream type
  2721. if (result.pid === this.programMapTable.video) {
  2722. result.streamType = streamTypes.H264_STREAM_TYPE;
  2723. } else if (result.pid === this.programMapTable.audio) {
  2724. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  2725. } else {
  2726. // if not video or audio, it is timed-metadata or unknown
  2727. // if unknown, streamType will be undefined
  2728. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  2729. }
  2730. result.type = 'pes';
  2731. result.data = packet.subarray(offset);
  2732. this.trigger('data', result);
  2733. };
  2734. };
  2735. _TransportParseStream.prototype = new stream();
  2736. _TransportParseStream.STREAM_TYPES = {
  2737. h264: 0x1b,
  2738. adts: 0x0f
  2739. };
  2740. /**
  2741. * Reconsistutes program elementary stream (PES) packets from parsed
  2742. * transport stream packets. That is, if you pipe an
  2743. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  2744. * events will be events which capture the bytes for individual PES
  2745. * packets plus relevant metadata that has been extracted from the
  2746. * container.
  2747. */
  2748. _ElementaryStream = function ElementaryStream() {
  2749. var self = this,
  2750. segmentHadPmt = false,
  2751. // PES packet fragments
  2752. video = {
  2753. data: [],
  2754. size: 0
  2755. },
  2756. audio = {
  2757. data: [],
  2758. size: 0
  2759. },
  2760. timedMetadata = {
  2761. data: [],
  2762. size: 0
  2763. },
  2764. programMapTable,
  2765. parsePes = function parsePes(payload, pes) {
  2766. var ptsDtsFlags;
  2767. var startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array
  2768. pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
  2769. // that are frame data that is continuing from the previous fragment. This
  2770. // is to check that the pes data is the start of a new pes payload
  2771. if (startPrefix !== 1) {
  2772. return;
  2773. } // get the packet length, this will be 0 for video
  2774. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  2775. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  2776. // and a DTS value. Determine what combination of values is
  2777. // available to work with.
  2778. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  2779. // performs all bitwise operations on 32-bit integers but javascript
  2780. // supports a much greater range (52-bits) of integer using standard
  2781. // mathematical operations.
  2782. // We construct a 31-bit value using bitwise operators over the 31
  2783. // most significant bits and then multiply by 4 (equal to a left-shift
  2784. // of 2) before we add the final 2 least significant bits of the
  2785. // timestamp (equal to an OR.)
  2786. if (ptsDtsFlags & 0xC0) {
  2787. // the PTS and DTS are not written out directly. For information
  2788. // on how they are encoded, see
  2789. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  2790. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  2791. pes.pts *= 4; // Left shift by 2
  2792. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  2793. pes.dts = pes.pts;
  2794. if (ptsDtsFlags & 0x40) {
  2795. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  2796. pes.dts *= 4; // Left shift by 2
  2797. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  2798. }
  2799. } // the data section starts immediately after the PES header.
  2800. // pes_header_data_length specifies the number of header bytes
  2801. // that follow the last byte of the field.
  2802. pes.data = payload.subarray(9 + payload[8]);
  2803. },
  2804. /**
  2805. * Pass completely parsed PES packets to the next stream in the pipeline
  2806. **/
  2807. flushStream = function flushStream(stream, type, forceFlush) {
  2808. var packetData = new Uint8Array(stream.size),
  2809. event = {
  2810. type: type
  2811. },
  2812. i = 0,
  2813. offset = 0,
  2814. packetFlushable = false,
  2815. fragment; // do nothing if there is not enough buffered data for a complete
  2816. // PES header
  2817. if (!stream.data.length || stream.size < 9) {
  2818. return;
  2819. }
  2820. event.trackId = stream.data[0].pid; // reassemble the packet
  2821. for (i = 0; i < stream.data.length; i++) {
  2822. fragment = stream.data[i];
  2823. packetData.set(fragment.data, offset);
  2824. offset += fragment.data.byteLength;
  2825. } // parse assembled packet's PES header
  2826. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  2827. // check that there is enough stream data to fill the packet
  2828. packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
  2829. if (forceFlush || packetFlushable) {
  2830. stream.size = 0;
  2831. stream.data.length = 0;
  2832. } // only emit packets that are complete. this is to avoid assembling
  2833. // incomplete PES packets due to poor segmentation
  2834. if (packetFlushable) {
  2835. self.trigger('data', event);
  2836. }
  2837. };
  2838. _ElementaryStream.prototype.init.call(this);
  2839. /**
  2840. * Identifies M2TS packet types and parses PES packets using metadata
  2841. * parsed from the PMT
  2842. **/
  2843. this.push = function (data) {
  2844. ({
  2845. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  2846. // have any meaningful metadata
  2847. },
  2848. pes: function pes() {
  2849. var stream, streamType;
  2850. switch (data.streamType) {
  2851. case streamTypes.H264_STREAM_TYPE:
  2852. stream = video;
  2853. streamType = 'video';
  2854. break;
  2855. case streamTypes.ADTS_STREAM_TYPE:
  2856. stream = audio;
  2857. streamType = 'audio';
  2858. break;
  2859. case streamTypes.METADATA_STREAM_TYPE:
  2860. stream = timedMetadata;
  2861. streamType = 'timed-metadata';
  2862. break;
  2863. default:
  2864. // ignore unknown stream types
  2865. return;
  2866. } // if a new packet is starting, we can flush the completed
  2867. // packet
  2868. if (data.payloadUnitStartIndicator) {
  2869. flushStream(stream, streamType, true);
  2870. } // buffer this fragment until we are sure we've received the
  2871. // complete payload
  2872. stream.data.push(data);
  2873. stream.size += data.data.byteLength;
  2874. },
  2875. pmt: function pmt() {
  2876. var event = {
  2877. type: 'metadata',
  2878. tracks: []
  2879. };
  2880. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  2881. if (programMapTable.video !== null) {
  2882. event.tracks.push({
  2883. timelineStartInfo: {
  2884. baseMediaDecodeTime: 0
  2885. },
  2886. id: +programMapTable.video,
  2887. codec: 'avc',
  2888. type: 'video'
  2889. });
  2890. }
  2891. if (programMapTable.audio !== null) {
  2892. event.tracks.push({
  2893. timelineStartInfo: {
  2894. baseMediaDecodeTime: 0
  2895. },
  2896. id: +programMapTable.audio,
  2897. codec: 'adts',
  2898. type: 'audio'
  2899. });
  2900. }
  2901. segmentHadPmt = true;
  2902. self.trigger('data', event);
  2903. }
  2904. })[data.type]();
  2905. };
  2906. this.reset = function () {
  2907. video.size = 0;
  2908. video.data.length = 0;
  2909. audio.size = 0;
  2910. audio.data.length = 0;
  2911. this.trigger('reset');
  2912. };
  2913. /**
  2914. * Flush any remaining input. Video PES packets may be of variable
  2915. * length. Normally, the start of a new video packet can trigger the
  2916. * finalization of the previous packet. That is not possible if no
  2917. * more video is forthcoming, however. In that case, some other
  2918. * mechanism (like the end of the file) has to be employed. When it is
  2919. * clear that no additional data is forthcoming, calling this method
  2920. * will flush the buffered packets.
  2921. */
  2922. this.flushStreams_ = function () {
  2923. // !!THIS ORDER IS IMPORTANT!!
  2924. // video first then audio
  2925. flushStream(video, 'video');
  2926. flushStream(audio, 'audio');
  2927. flushStream(timedMetadata, 'timed-metadata');
  2928. };
  2929. this.flush = function () {
  2930. // if on flush we haven't had a pmt emitted
  2931. // and we have a pmt to emit. emit the pmt
  2932. // so that we trigger a trackinfo downstream.
  2933. if (!segmentHadPmt && programMapTable) {
  2934. var pmt = {
  2935. type: 'metadata',
  2936. tracks: []
  2937. }; // translate audio and video streams to tracks
  2938. if (programMapTable.video !== null) {
  2939. pmt.tracks.push({
  2940. timelineStartInfo: {
  2941. baseMediaDecodeTime: 0
  2942. },
  2943. id: +programMapTable.video,
  2944. codec: 'avc',
  2945. type: 'video'
  2946. });
  2947. }
  2948. if (programMapTable.audio !== null) {
  2949. pmt.tracks.push({
  2950. timelineStartInfo: {
  2951. baseMediaDecodeTime: 0
  2952. },
  2953. id: +programMapTable.audio,
  2954. codec: 'adts',
  2955. type: 'audio'
  2956. });
  2957. }
  2958. self.trigger('data', pmt);
  2959. }
  2960. segmentHadPmt = false;
  2961. this.flushStreams_();
  2962. this.trigger('done');
  2963. };
  2964. };
  2965. _ElementaryStream.prototype = new stream();
  2966. var m2ts = {
  2967. PAT_PID: 0x0000,
  2968. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  2969. TransportPacketStream: _TransportPacketStream,
  2970. TransportParseStream: _TransportParseStream,
  2971. ElementaryStream: _ElementaryStream,
  2972. TimestampRolloverStream: TimestampRolloverStream,
  2973. CaptionStream: captionStream.CaptionStream,
  2974. Cea608Stream: captionStream.Cea608Stream,
  2975. Cea708Stream: captionStream.Cea708Stream,
  2976. MetadataStream: metadataStream
  2977. };
  2978. for (var type in streamTypes) {
  2979. if (streamTypes.hasOwnProperty(type)) {
  2980. m2ts[type] = streamTypes[type];
  2981. }
  2982. }
  2983. var m2ts_1 = m2ts;
  2984. /**
  2985. * mux.js
  2986. *
  2987. * Copyright (c) Brightcove
  2988. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  2989. */
  2990. var ONE_SECOND_IN_TS$1 = 90000,
  2991. // 90kHz clock
  2992. secondsToVideoTs,
  2993. secondsToAudioTs,
  2994. videoTsToSeconds,
  2995. audioTsToSeconds,
  2996. audioTsToVideoTs,
  2997. videoTsToAudioTs,
  2998. metadataTsToSeconds;
  2999. secondsToVideoTs = function secondsToVideoTs(seconds) {
  3000. return seconds * ONE_SECOND_IN_TS$1;
  3001. };
  3002. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  3003. return seconds * sampleRate;
  3004. };
  3005. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  3006. return timestamp / ONE_SECOND_IN_TS$1;
  3007. };
  3008. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  3009. return timestamp / sampleRate;
  3010. };
  3011. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  3012. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  3013. };
  3014. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  3015. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  3016. };
  3017. /**
  3018. * Adjust ID3 tag or caption timing information by the timeline pts values
  3019. * (if keepOriginalTimestamps is false) and convert to seconds
  3020. */
  3021. metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
  3022. return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
  3023. };
  3024. var clock = {
  3025. ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$1,
  3026. secondsToVideoTs: secondsToVideoTs,
  3027. secondsToAudioTs: secondsToAudioTs,
  3028. videoTsToSeconds: videoTsToSeconds,
  3029. audioTsToSeconds: audioTsToSeconds,
  3030. audioTsToVideoTs: audioTsToVideoTs,
  3031. videoTsToAudioTs: videoTsToAudioTs,
  3032. metadataTsToSeconds: metadataTsToSeconds
  3033. };
  3034. var ONE_SECOND_IN_TS = clock.ONE_SECOND_IN_TS;
  3035. var _AdtsStream;
  3036. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  3037. /*
  3038. * Accepts a ElementaryStream and emits data events with parsed
  3039. * AAC Audio Frames of the individual packets. Input audio in ADTS
  3040. * format is unpacked and re-emitted as AAC frames.
  3041. *
  3042. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  3043. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  3044. */
  3045. _AdtsStream = function AdtsStream(handlePartialSegments) {
  3046. var buffer,
  3047. frameNum = 0;
  3048. _AdtsStream.prototype.init.call(this);
  3049. this.skipWarn_ = function (start, end) {
  3050. this.trigger('log', {
  3051. level: 'warn',
  3052. message: "adts skiping bytes " + start + " to " + end + " in frame " + frameNum + " outside syncword"
  3053. });
  3054. };
  3055. this.push = function (packet) {
  3056. var i = 0,
  3057. frameLength,
  3058. protectionSkipBytes,
  3059. oldBuffer,
  3060. sampleCount,
  3061. adtsFrameDuration;
  3062. if (!handlePartialSegments) {
  3063. frameNum = 0;
  3064. }
  3065. if (packet.type !== 'audio') {
  3066. // ignore non-audio data
  3067. return;
  3068. } // Prepend any data in the buffer to the input data so that we can parse
  3069. // aac frames the cross a PES packet boundary
  3070. if (buffer && buffer.length) {
  3071. oldBuffer = buffer;
  3072. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  3073. buffer.set(oldBuffer);
  3074. buffer.set(packet.data, oldBuffer.byteLength);
  3075. } else {
  3076. buffer = packet.data;
  3077. } // unpack any ADTS frames which have been fully received
  3078. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  3079. var skip; // We use i + 7 here because we want to be able to parse the entire header.
  3080. // If we don't have enough bytes to do that, then we definitely won't have a full frame.
  3081. while (i + 7 < buffer.length) {
  3082. // Look for the start of an ADTS header..
  3083. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  3084. if (typeof skip !== 'number') {
  3085. skip = i;
  3086. } // If a valid header was not found, jump one forward and attempt to
  3087. // find a valid ADTS header starting at the next byte
  3088. i++;
  3089. continue;
  3090. }
  3091. if (typeof skip === 'number') {
  3092. this.skipWarn_(skip, i);
  3093. skip = null;
  3094. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  3095. // end of the ADTS header
  3096. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  3097. // end of the sync sequence
  3098. // NOTE: frame length includes the size of the header
  3099. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  3100. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  3101. adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; // If we don't have enough data to actually finish this ADTS frame,
  3102. // then we have to wait for more data
  3103. if (buffer.byteLength - i < frameLength) {
  3104. break;
  3105. } // Otherwise, deliver the complete AAC frame
  3106. this.trigger('data', {
  3107. pts: packet.pts + frameNum * adtsFrameDuration,
  3108. dts: packet.dts + frameNum * adtsFrameDuration,
  3109. sampleCount: sampleCount,
  3110. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  3111. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  3112. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  3113. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  3114. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  3115. samplesize: 16,
  3116. // data is the frame without it's header
  3117. data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength)
  3118. });
  3119. frameNum++;
  3120. i += frameLength;
  3121. }
  3122. if (typeof skip === 'number') {
  3123. this.skipWarn_(skip, i);
  3124. skip = null;
  3125. } // remove processed bytes from the buffer.
  3126. buffer = buffer.subarray(i);
  3127. };
  3128. this.flush = function () {
  3129. frameNum = 0;
  3130. this.trigger('done');
  3131. };
  3132. this.reset = function () {
  3133. buffer = void 0;
  3134. this.trigger('reset');
  3135. };
  3136. this.endTimeline = function () {
  3137. buffer = void 0;
  3138. this.trigger('endedtimeline');
  3139. };
  3140. };
  3141. _AdtsStream.prototype = new stream();
  3142. var adts = _AdtsStream;
  3143. /**
  3144. * mux.js
  3145. *
  3146. * Copyright (c) Brightcove
  3147. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  3148. */
  3149. var ExpGolomb;
  3150. /**
  3151. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  3152. * scheme used by h264.
  3153. */
  3154. ExpGolomb = function ExpGolomb(workingData) {
  3155. var // the number of bytes left to examine in workingData
  3156. workingBytesAvailable = workingData.byteLength,
  3157. // the current word being examined
  3158. workingWord = 0,
  3159. // :uint
  3160. // the number of bits left to examine in the current word
  3161. workingBitsAvailable = 0; // :uint;
  3162. // ():uint
  3163. this.length = function () {
  3164. return 8 * workingBytesAvailable;
  3165. }; // ():uint
  3166. this.bitsAvailable = function () {
  3167. return 8 * workingBytesAvailable + workingBitsAvailable;
  3168. }; // ():void
  3169. this.loadWord = function () {
  3170. var position = workingData.byteLength - workingBytesAvailable,
  3171. workingBytes = new Uint8Array(4),
  3172. availableBytes = Math.min(4, workingBytesAvailable);
  3173. if (availableBytes === 0) {
  3174. throw new Error('no bytes available');
  3175. }
  3176. workingBytes.set(workingData.subarray(position, position + availableBytes));
  3177. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  3178. workingBitsAvailable = availableBytes * 8;
  3179. workingBytesAvailable -= availableBytes;
  3180. }; // (count:int):void
  3181. this.skipBits = function (count) {
  3182. var skipBytes; // :int
  3183. if (workingBitsAvailable > count) {
  3184. workingWord <<= count;
  3185. workingBitsAvailable -= count;
  3186. } else {
  3187. count -= workingBitsAvailable;
  3188. skipBytes = Math.floor(count / 8);
  3189. count -= skipBytes * 8;
  3190. workingBytesAvailable -= skipBytes;
  3191. this.loadWord();
  3192. workingWord <<= count;
  3193. workingBitsAvailable -= count;
  3194. }
  3195. }; // (size:int):uint
  3196. this.readBits = function (size) {
  3197. var bits = Math.min(workingBitsAvailable, size),
  3198. // :uint
  3199. valu = workingWord >>> 32 - bits; // :uint
  3200. // if size > 31, handle error
  3201. workingBitsAvailable -= bits;
  3202. if (workingBitsAvailable > 0) {
  3203. workingWord <<= bits;
  3204. } else if (workingBytesAvailable > 0) {
  3205. this.loadWord();
  3206. }
  3207. bits = size - bits;
  3208. if (bits > 0) {
  3209. return valu << bits | this.readBits(bits);
  3210. }
  3211. return valu;
  3212. }; // ():uint
  3213. this.skipLeadingZeros = function () {
  3214. var leadingZeroCount; // :uint
  3215. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  3216. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  3217. // the first bit of working word is 1
  3218. workingWord <<= leadingZeroCount;
  3219. workingBitsAvailable -= leadingZeroCount;
  3220. return leadingZeroCount;
  3221. }
  3222. } // we exhausted workingWord and still have not found a 1
  3223. this.loadWord();
  3224. return leadingZeroCount + this.skipLeadingZeros();
  3225. }; // ():void
  3226. this.skipUnsignedExpGolomb = function () {
  3227. this.skipBits(1 + this.skipLeadingZeros());
  3228. }; // ():void
  3229. this.skipExpGolomb = function () {
  3230. this.skipBits(1 + this.skipLeadingZeros());
  3231. }; // ():uint
  3232. this.readUnsignedExpGolomb = function () {
  3233. var clz = this.skipLeadingZeros(); // :uint
  3234. return this.readBits(clz + 1) - 1;
  3235. }; // ():int
  3236. this.readExpGolomb = function () {
  3237. var valu = this.readUnsignedExpGolomb(); // :int
  3238. if (0x01 & valu) {
  3239. // the number is odd if the low order bit is set
  3240. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  3241. }
  3242. return -1 * (valu >>> 1); // divide by two then make it negative
  3243. }; // Some convenience functions
  3244. // :Boolean
  3245. this.readBoolean = function () {
  3246. return this.readBits(1) === 1;
  3247. }; // ():int
  3248. this.readUnsignedByte = function () {
  3249. return this.readBits(8);
  3250. };
  3251. this.loadWord();
  3252. };
  3253. var expGolomb = ExpGolomb;
  3254. var _H264Stream, _NalByteStream;
  3255. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  3256. /**
  3257. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  3258. */
  3259. _NalByteStream = function NalByteStream() {
  3260. var syncPoint = 0,
  3261. i,
  3262. buffer;
  3263. _NalByteStream.prototype.init.call(this);
  3264. /*
  3265. * Scans a byte stream and triggers a data event with the NAL units found.
  3266. * @param {Object} data Event received from H264Stream
  3267. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  3268. *
  3269. * @see H264Stream.push
  3270. */
  3271. this.push = function (data) {
  3272. var swapBuffer;
  3273. if (!buffer) {
  3274. buffer = data.data;
  3275. } else {
  3276. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  3277. swapBuffer.set(buffer);
  3278. swapBuffer.set(data.data, buffer.byteLength);
  3279. buffer = swapBuffer;
  3280. }
  3281. var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
  3282. // scan for NAL unit boundaries
  3283. // a match looks like this:
  3284. // 0 0 1 .. NAL .. 0 0 1
  3285. // ^ sync point ^ i
  3286. // or this:
  3287. // 0 0 1 .. NAL .. 0 0 0
  3288. // ^ sync point ^ i
  3289. // advance the sync point to a NAL start, if necessary
  3290. for (; syncPoint < len - 3; syncPoint++) {
  3291. if (buffer[syncPoint + 2] === 1) {
  3292. // the sync point is properly aligned
  3293. i = syncPoint + 5;
  3294. break;
  3295. }
  3296. }
  3297. while (i < len) {
  3298. // look at the current byte to determine if we've hit the end of
  3299. // a NAL unit boundary
  3300. switch (buffer[i]) {
  3301. case 0:
  3302. // skip past non-sync sequences
  3303. if (buffer[i - 1] !== 0) {
  3304. i += 2;
  3305. break;
  3306. } else if (buffer[i - 2] !== 0) {
  3307. i++;
  3308. break;
  3309. } // deliver the NAL unit if it isn't empty
  3310. if (syncPoint + 3 !== i - 2) {
  3311. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  3312. } // drop trailing zeroes
  3313. do {
  3314. i++;
  3315. } while (buffer[i] !== 1 && i < len);
  3316. syncPoint = i - 2;
  3317. i += 3;
  3318. break;
  3319. case 1:
  3320. // skip past non-sync sequences
  3321. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  3322. i += 3;
  3323. break;
  3324. } // deliver the NAL unit
  3325. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  3326. syncPoint = i - 2;
  3327. i += 3;
  3328. break;
  3329. default:
  3330. // the current byte isn't a one or zero, so it cannot be part
  3331. // of a sync sequence
  3332. i += 3;
  3333. break;
  3334. }
  3335. } // filter out the NAL units that were delivered
  3336. buffer = buffer.subarray(syncPoint);
  3337. i -= syncPoint;
  3338. syncPoint = 0;
  3339. };
  3340. this.reset = function () {
  3341. buffer = null;
  3342. syncPoint = 0;
  3343. this.trigger('reset');
  3344. };
  3345. this.flush = function () {
  3346. // deliver the last buffered NAL unit
  3347. if (buffer && buffer.byteLength > 3) {
  3348. this.trigger('data', buffer.subarray(syncPoint + 3));
  3349. } // reset the stream state
  3350. buffer = null;
  3351. syncPoint = 0;
  3352. this.trigger('done');
  3353. };
  3354. this.endTimeline = function () {
  3355. this.flush();
  3356. this.trigger('endedtimeline');
  3357. };
  3358. };
  3359. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  3360. // see Recommendation ITU-T H.264 (4/2013),
  3361. // 7.3.2.1.1 Sequence parameter set data syntax
  3362. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  3363. 100: true,
  3364. 110: true,
  3365. 122: true,
  3366. 244: true,
  3367. 44: true,
  3368. 83: true,
  3369. 86: true,
  3370. 118: true,
  3371. 128: true,
  3372. // TODO: the three profiles below don't
  3373. // appear to have sps data in the specificiation anymore?
  3374. 138: true,
  3375. 139: true,
  3376. 134: true
  3377. };
  3378. /**
  3379. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  3380. * events.
  3381. */
  3382. _H264Stream = function H264Stream() {
  3383. var nalByteStream = new _NalByteStream(),
  3384. self,
  3385. trackId,
  3386. currentPts,
  3387. currentDts,
  3388. discardEmulationPreventionBytes,
  3389. readSequenceParameterSet,
  3390. skipScalingList;
  3391. _H264Stream.prototype.init.call(this);
  3392. self = this;
  3393. /*
  3394. * Pushes a packet from a stream onto the NalByteStream
  3395. *
  3396. * @param {Object} packet - A packet received from a stream
  3397. * @param {Uint8Array} packet.data - The raw bytes of the packet
  3398. * @param {Number} packet.dts - Decode timestamp of the packet
  3399. * @param {Number} packet.pts - Presentation timestamp of the packet
  3400. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  3401. * @param {('video'|'audio')} packet.type - The type of packet
  3402. *
  3403. */
  3404. this.push = function (packet) {
  3405. if (packet.type !== 'video') {
  3406. return;
  3407. }
  3408. trackId = packet.trackId;
  3409. currentPts = packet.pts;
  3410. currentDts = packet.dts;
  3411. nalByteStream.push(packet);
  3412. };
  3413. /*
  3414. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  3415. * for the NALUs to the next stream component.
  3416. * Also, preprocess caption and sequence parameter NALUs.
  3417. *
  3418. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  3419. * @see NalByteStream.push
  3420. */
  3421. nalByteStream.on('data', function (data) {
  3422. var event = {
  3423. trackId: trackId,
  3424. pts: currentPts,
  3425. dts: currentDts,
  3426. data: data,
  3427. nalUnitTypeCode: data[0] & 0x1f
  3428. };
  3429. switch (event.nalUnitTypeCode) {
  3430. case 0x05:
  3431. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  3432. break;
  3433. case 0x06:
  3434. event.nalUnitType = 'sei_rbsp';
  3435. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  3436. break;
  3437. case 0x07:
  3438. event.nalUnitType = 'seq_parameter_set_rbsp';
  3439. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  3440. event.config = readSequenceParameterSet(event.escapedRBSP);
  3441. break;
  3442. case 0x08:
  3443. event.nalUnitType = 'pic_parameter_set_rbsp';
  3444. break;
  3445. case 0x09:
  3446. event.nalUnitType = 'access_unit_delimiter_rbsp';
  3447. break;
  3448. } // This triggers data on the H264Stream
  3449. self.trigger('data', event);
  3450. });
  3451. nalByteStream.on('done', function () {
  3452. self.trigger('done');
  3453. });
  3454. nalByteStream.on('partialdone', function () {
  3455. self.trigger('partialdone');
  3456. });
  3457. nalByteStream.on('reset', function () {
  3458. self.trigger('reset');
  3459. });
  3460. nalByteStream.on('endedtimeline', function () {
  3461. self.trigger('endedtimeline');
  3462. });
  3463. this.flush = function () {
  3464. nalByteStream.flush();
  3465. };
  3466. this.partialFlush = function () {
  3467. nalByteStream.partialFlush();
  3468. };
  3469. this.reset = function () {
  3470. nalByteStream.reset();
  3471. };
  3472. this.endTimeline = function () {
  3473. nalByteStream.endTimeline();
  3474. };
  3475. /**
  3476. * Advance the ExpGolomb decoder past a scaling list. The scaling
  3477. * list is optionally transmitted as part of a sequence parameter
  3478. * set and is not relevant to transmuxing.
  3479. * @param count {number} the number of entries in this scaling list
  3480. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  3481. * start of a scaling list
  3482. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  3483. */
  3484. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  3485. var lastScale = 8,
  3486. nextScale = 8,
  3487. j,
  3488. deltaScale;
  3489. for (j = 0; j < count; j++) {
  3490. if (nextScale !== 0) {
  3491. deltaScale = expGolombDecoder.readExpGolomb();
  3492. nextScale = (lastScale + deltaScale + 256) % 256;
  3493. }
  3494. lastScale = nextScale === 0 ? lastScale : nextScale;
  3495. }
  3496. };
  3497. /**
  3498. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  3499. * Sequence Payload"
  3500. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  3501. * unit
  3502. * @return {Uint8Array} the RBSP without any Emulation
  3503. * Prevention Bytes
  3504. */
  3505. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  3506. var length = data.byteLength,
  3507. emulationPreventionBytesPositions = [],
  3508. i = 1,
  3509. newLength,
  3510. newData; // Find all `Emulation Prevention Bytes`
  3511. while (i < length - 2) {
  3512. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  3513. emulationPreventionBytesPositions.push(i + 2);
  3514. i += 2;
  3515. } else {
  3516. i++;
  3517. }
  3518. } // If no Emulation Prevention Bytes were found just return the original
  3519. // array
  3520. if (emulationPreventionBytesPositions.length === 0) {
  3521. return data;
  3522. } // Create a new array to hold the NAL unit data
  3523. newLength = length - emulationPreventionBytesPositions.length;
  3524. newData = new Uint8Array(newLength);
  3525. var sourceIndex = 0;
  3526. for (i = 0; i < newLength; sourceIndex++, i++) {
  3527. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  3528. // Skip this byte
  3529. sourceIndex++; // Remove this position index
  3530. emulationPreventionBytesPositions.shift();
  3531. }
  3532. newData[i] = data[sourceIndex];
  3533. }
  3534. return newData;
  3535. };
  3536. /**
  3537. * Read a sequence parameter set and return some interesting video
  3538. * properties. A sequence parameter set is the H264 metadata that
  3539. * describes the properties of upcoming video frames.
  3540. * @param data {Uint8Array} the bytes of a sequence parameter set
  3541. * @return {object} an object with configuration parsed from the
  3542. * sequence parameter set, including the dimensions of the
  3543. * associated video frames.
  3544. */
  3545. readSequenceParameterSet = function readSequenceParameterSet(data) {
  3546. var frameCropLeftOffset = 0,
  3547. frameCropRightOffset = 0,
  3548. frameCropTopOffset = 0,
  3549. frameCropBottomOffset = 0,
  3550. expGolombDecoder,
  3551. profileIdc,
  3552. levelIdc,
  3553. profileCompatibility,
  3554. chromaFormatIdc,
  3555. picOrderCntType,
  3556. numRefFramesInPicOrderCntCycle,
  3557. picWidthInMbsMinus1,
  3558. picHeightInMapUnitsMinus1,
  3559. frameMbsOnlyFlag,
  3560. scalingListCount,
  3561. sarRatio = [1, 1],
  3562. aspectRatioIdc,
  3563. i;
  3564. expGolombDecoder = new expGolomb(data);
  3565. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  3566. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  3567. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  3568. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  3569. // some profiles have more optional data we don't need
  3570. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  3571. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  3572. if (chromaFormatIdc === 3) {
  3573. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  3574. }
  3575. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  3576. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  3577. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  3578. if (expGolombDecoder.readBoolean()) {
  3579. // seq_scaling_matrix_present_flag
  3580. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  3581. for (i = 0; i < scalingListCount; i++) {
  3582. if (expGolombDecoder.readBoolean()) {
  3583. // seq_scaling_list_present_flag[ i ]
  3584. if (i < 6) {
  3585. skipScalingList(16, expGolombDecoder);
  3586. } else {
  3587. skipScalingList(64, expGolombDecoder);
  3588. }
  3589. }
  3590. }
  3591. }
  3592. }
  3593. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  3594. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  3595. if (picOrderCntType === 0) {
  3596. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  3597. } else if (picOrderCntType === 1) {
  3598. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  3599. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  3600. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  3601. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  3602. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  3603. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  3604. }
  3605. }
  3606. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  3607. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  3608. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  3609. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  3610. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  3611. if (frameMbsOnlyFlag === 0) {
  3612. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  3613. }
  3614. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  3615. if (expGolombDecoder.readBoolean()) {
  3616. // frame_cropping_flag
  3617. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  3618. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  3619. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  3620. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  3621. }
  3622. if (expGolombDecoder.readBoolean()) {
  3623. // vui_parameters_present_flag
  3624. if (expGolombDecoder.readBoolean()) {
  3625. // aspect_ratio_info_present_flag
  3626. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  3627. switch (aspectRatioIdc) {
  3628. case 1:
  3629. sarRatio = [1, 1];
  3630. break;
  3631. case 2:
  3632. sarRatio = [12, 11];
  3633. break;
  3634. case 3:
  3635. sarRatio = [10, 11];
  3636. break;
  3637. case 4:
  3638. sarRatio = [16, 11];
  3639. break;
  3640. case 5:
  3641. sarRatio = [40, 33];
  3642. break;
  3643. case 6:
  3644. sarRatio = [24, 11];
  3645. break;
  3646. case 7:
  3647. sarRatio = [20, 11];
  3648. break;
  3649. case 8:
  3650. sarRatio = [32, 11];
  3651. break;
  3652. case 9:
  3653. sarRatio = [80, 33];
  3654. break;
  3655. case 10:
  3656. sarRatio = [18, 11];
  3657. break;
  3658. case 11:
  3659. sarRatio = [15, 11];
  3660. break;
  3661. case 12:
  3662. sarRatio = [64, 33];
  3663. break;
  3664. case 13:
  3665. sarRatio = [160, 99];
  3666. break;
  3667. case 14:
  3668. sarRatio = [4, 3];
  3669. break;
  3670. case 15:
  3671. sarRatio = [3, 2];
  3672. break;
  3673. case 16:
  3674. sarRatio = [2, 1];
  3675. break;
  3676. case 255:
  3677. {
  3678. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  3679. break;
  3680. }
  3681. }
  3682. if (sarRatio) {
  3683. sarRatio[0] / sarRatio[1];
  3684. }
  3685. }
  3686. }
  3687. return {
  3688. profileIdc: profileIdc,
  3689. levelIdc: levelIdc,
  3690. profileCompatibility: profileCompatibility,
  3691. width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
  3692. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
  3693. // sar is sample aspect ratio
  3694. sarRatio: sarRatio
  3695. };
  3696. };
  3697. };
  3698. _H264Stream.prototype = new stream();
  3699. var h264 = {
  3700. H264Stream: _H264Stream,
  3701. NalByteStream: _NalByteStream
  3702. };
  3703. /**
  3704. * The final stage of the transmuxer that emits the flv tags
  3705. * for audio, video, and metadata. Also tranlates in time and
  3706. * outputs caption data and id3 cues.
  3707. */
  3708. var CoalesceStream = function CoalesceStream(options) {
  3709. // Number of Tracks per output segment
  3710. // If greater than 1, we combine multiple
  3711. // tracks into a single segment
  3712. this.numberOfTracks = 0;
  3713. this.metadataStream = options.metadataStream;
  3714. this.videoTags = [];
  3715. this.audioTags = [];
  3716. this.videoTrack = null;
  3717. this.audioTrack = null;
  3718. this.pendingCaptions = [];
  3719. this.pendingMetadata = [];
  3720. this.pendingTracks = 0;
  3721. this.processedTracks = 0;
  3722. CoalesceStream.prototype.init.call(this); // Take output from multiple
  3723. this.push = function (output) {
  3724. // buffer incoming captions until the associated video segment
  3725. // finishes
  3726. if (output.text) {
  3727. return this.pendingCaptions.push(output);
  3728. } // buffer incoming id3 tags until the final flush
  3729. if (output.frames) {
  3730. return this.pendingMetadata.push(output);
  3731. }
  3732. if (output.track.type === 'video') {
  3733. this.videoTrack = output.track;
  3734. this.videoTags = output.tags;
  3735. this.pendingTracks++;
  3736. }
  3737. if (output.track.type === 'audio') {
  3738. this.audioTrack = output.track;
  3739. this.audioTags = output.tags;
  3740. this.pendingTracks++;
  3741. }
  3742. };
  3743. };
  3744. CoalesceStream.prototype = new stream();
  3745. CoalesceStream.prototype.flush = function (flushSource) {
  3746. var id3,
  3747. caption,
  3748. i,
  3749. timelineStartPts,
  3750. event = {
  3751. tags: {},
  3752. captions: [],
  3753. captionStreams: {},
  3754. metadata: []
  3755. };
  3756. if (this.pendingTracks < this.numberOfTracks) {
  3757. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  3758. // Return because we haven't received a flush from a data-generating
  3759. // portion of the segment (meaning that we have only recieved meta-data
  3760. // or captions.)
  3761. return;
  3762. } else if (this.pendingTracks === 0) {
  3763. // In the case where we receive a flush without any data having been
  3764. // received we consider it an emitted track for the purposes of coalescing
  3765. // `done` events.
  3766. // We do this for the case where there is an audio and video track in the
  3767. // segment but no audio data. (seen in several playlists with alternate
  3768. // audio tracks and no audio present in the main TS segments.)
  3769. this.processedTracks++;
  3770. if (this.processedTracks < this.numberOfTracks) {
  3771. return;
  3772. }
  3773. }
  3774. }
  3775. this.processedTracks += this.pendingTracks;
  3776. this.pendingTracks = 0;
  3777. if (this.processedTracks < this.numberOfTracks) {
  3778. return;
  3779. }
  3780. if (this.videoTrack) {
  3781. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  3782. } else if (this.audioTrack) {
  3783. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  3784. }
  3785. event.tags.videoTags = this.videoTags;
  3786. event.tags.audioTags = this.audioTags; // Translate caption PTS times into second offsets into the
  3787. // video timeline for the segment, and add track info
  3788. for (i = 0; i < this.pendingCaptions.length; i++) {
  3789. caption = this.pendingCaptions[i];
  3790. caption.startTime = caption.startPts - timelineStartPts;
  3791. caption.startTime /= 90e3;
  3792. caption.endTime = caption.endPts - timelineStartPts;
  3793. caption.endTime /= 90e3;
  3794. event.captionStreams[caption.stream] = true;
  3795. event.captions.push(caption);
  3796. } // Translate ID3 frame PTS times into second offsets into the
  3797. // video timeline for the segment
  3798. for (i = 0; i < this.pendingMetadata.length; i++) {
  3799. id3 = this.pendingMetadata[i];
  3800. id3.cueTime = id3.pts - timelineStartPts;
  3801. id3.cueTime /= 90e3;
  3802. event.metadata.push(id3);
  3803. } // We add this to every single emitted segment even though we only need
  3804. // it for the first
  3805. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  3806. this.videoTrack = null;
  3807. this.audioTrack = null;
  3808. this.videoTags = [];
  3809. this.audioTags = [];
  3810. this.pendingCaptions.length = 0;
  3811. this.pendingMetadata.length = 0;
  3812. this.pendingTracks = 0;
  3813. this.processedTracks = 0; // Emit the final segment
  3814. this.trigger('data', event);
  3815. this.trigger('done');
  3816. };
  3817. var coalesceStream = CoalesceStream;
  3818. /**
  3819. * mux.js
  3820. *
  3821. * Copyright (c) Brightcove
  3822. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  3823. */
  3824. var TagList = function TagList() {
  3825. var self = this;
  3826. this.list = [];
  3827. this.push = function (tag) {
  3828. this.list.push({
  3829. bytes: tag.bytes,
  3830. dts: tag.dts,
  3831. pts: tag.pts,
  3832. keyFrame: tag.keyFrame,
  3833. metaDataTag: tag.metaDataTag
  3834. });
  3835. };
  3836. Object.defineProperty(this, 'length', {
  3837. get: function get() {
  3838. return self.list.length;
  3839. }
  3840. });
  3841. };
  3842. var tagList = TagList;
  3843. var H264Stream = h264.H264Stream;
  3844. var _Transmuxer, _VideoSegmentStream, _AudioSegmentStream, collectTimelineInfo, metaDataTag, extraDataTag;
  3845. /**
  3846. * Store information about the start and end of the tracka and the
  3847. * duration for each frame/sample we process in order to calculate
  3848. * the baseMediaDecodeTime
  3849. */
  3850. collectTimelineInfo = function collectTimelineInfo(track, data) {
  3851. if (typeof data.pts === 'number') {
  3852. if (track.timelineStartInfo.pts === undefined) {
  3853. track.timelineStartInfo.pts = data.pts;
  3854. } else {
  3855. track.timelineStartInfo.pts = Math.min(track.timelineStartInfo.pts, data.pts);
  3856. }
  3857. }
  3858. if (typeof data.dts === 'number') {
  3859. if (track.timelineStartInfo.dts === undefined) {
  3860. track.timelineStartInfo.dts = data.dts;
  3861. } else {
  3862. track.timelineStartInfo.dts = Math.min(track.timelineStartInfo.dts, data.dts);
  3863. }
  3864. }
  3865. };
  3866. metaDataTag = function metaDataTag(track, pts) {
  3867. var tag = new flvTag(flvTag.METADATA_TAG); // :FlvTag
  3868. tag.dts = pts;
  3869. tag.pts = pts;
  3870. tag.writeMetaDataDouble('videocodecid', 7);
  3871. tag.writeMetaDataDouble('width', track.width);
  3872. tag.writeMetaDataDouble('height', track.height);
  3873. return tag;
  3874. };
  3875. extraDataTag = function extraDataTag(track, pts) {
  3876. var i,
  3877. tag = new flvTag(flvTag.VIDEO_TAG, true);
  3878. tag.dts = pts;
  3879. tag.pts = pts;
  3880. tag.writeByte(0x01); // version
  3881. tag.writeByte(track.profileIdc); // profile
  3882. tag.writeByte(track.profileCompatibility); // compatibility
  3883. tag.writeByte(track.levelIdc); // level
  3884. tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
  3885. tag.writeByte(0xE0 | 0x01); // reserved (3 bits), num of SPS (5 bits)
  3886. tag.writeShort(track.sps[0].length); // data of SPS
  3887. tag.writeBytes(track.sps[0]); // SPS
  3888. tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?)
  3889. for (i = 0; i < track.pps.length; ++i) {
  3890. tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS
  3891. tag.writeBytes(track.pps[i]); // data of PPS
  3892. }
  3893. return tag;
  3894. };
  3895. /**
  3896. * Constructs a single-track, media segment from AAC data
  3897. * events. The output of this stream can be fed to flash.
  3898. */
  3899. _AudioSegmentStream = function AudioSegmentStream(track) {
  3900. var adtsFrames = [],
  3901. videoKeyFrames = [],
  3902. oldExtraData;
  3903. _AudioSegmentStream.prototype.init.call(this);
  3904. this.push = function (data) {
  3905. collectTimelineInfo(track, data);
  3906. if (track) {
  3907. track.audioobjecttype = data.audioobjecttype;
  3908. track.channelcount = data.channelcount;
  3909. track.samplerate = data.samplerate;
  3910. track.samplingfrequencyindex = data.samplingfrequencyindex;
  3911. track.samplesize = data.samplesize;
  3912. track.extraData = track.audioobjecttype << 11 | track.samplingfrequencyindex << 7 | track.channelcount << 3;
  3913. }
  3914. data.pts = Math.round(data.pts / 90);
  3915. data.dts = Math.round(data.dts / 90); // buffer audio data until end() is called
  3916. adtsFrames.push(data);
  3917. };
  3918. this.flush = function () {
  3919. var currentFrame,
  3920. adtsFrame,
  3921. lastMetaPts,
  3922. tags = new tagList(); // return early if no audio data has been observed
  3923. if (adtsFrames.length === 0) {
  3924. this.trigger('done', 'AudioSegmentStream');
  3925. return;
  3926. }
  3927. lastMetaPts = -Infinity;
  3928. while (adtsFrames.length) {
  3929. currentFrame = adtsFrames.shift(); // write out a metadata frame at every video key frame
  3930. if (videoKeyFrames.length && currentFrame.pts >= videoKeyFrames[0]) {
  3931. lastMetaPts = videoKeyFrames.shift();
  3932. this.writeMetaDataTags(tags, lastMetaPts);
  3933. } // also write out metadata tags every 1 second so that the decoder
  3934. // is re-initialized quickly after seeking into a different
  3935. // audio configuration.
  3936. if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) {
  3937. this.writeMetaDataTags(tags, currentFrame.pts);
  3938. oldExtraData = track.extraData;
  3939. lastMetaPts = currentFrame.pts;
  3940. }
  3941. adtsFrame = new flvTag(flvTag.AUDIO_TAG);
  3942. adtsFrame.pts = currentFrame.pts;
  3943. adtsFrame.dts = currentFrame.dts;
  3944. adtsFrame.writeBytes(currentFrame.data);
  3945. tags.push(adtsFrame.finalize());
  3946. }
  3947. videoKeyFrames.length = 0;
  3948. oldExtraData = null;
  3949. this.trigger('data', {
  3950. track: track,
  3951. tags: tags.list
  3952. });
  3953. this.trigger('done', 'AudioSegmentStream');
  3954. };
  3955. this.writeMetaDataTags = function (tags, pts) {
  3956. var adtsFrame;
  3957. adtsFrame = new flvTag(flvTag.METADATA_TAG); // For audio, DTS is always the same as PTS. We want to set the DTS
  3958. // however so we can compare with video DTS to determine approximate
  3959. // packet order
  3960. adtsFrame.pts = pts;
  3961. adtsFrame.dts = pts; // AAC is always 10
  3962. adtsFrame.writeMetaDataDouble('audiocodecid', 10);
  3963. adtsFrame.writeMetaDataBoolean('stereo', track.channelcount === 2);
  3964. adtsFrame.writeMetaDataDouble('audiosamplerate', track.samplerate); // Is AAC always 16 bit?
  3965. adtsFrame.writeMetaDataDouble('audiosamplesize', 16);
  3966. tags.push(adtsFrame.finalize());
  3967. adtsFrame = new flvTag(flvTag.AUDIO_TAG, true); // For audio, DTS is always the same as PTS. We want to set the DTS
  3968. // however so we can compare with video DTS to determine approximate
  3969. // packet order
  3970. adtsFrame.pts = pts;
  3971. adtsFrame.dts = pts;
  3972. adtsFrame.view.setUint16(adtsFrame.position, track.extraData);
  3973. adtsFrame.position += 2;
  3974. adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position);
  3975. tags.push(adtsFrame.finalize());
  3976. };
  3977. this.onVideoKeyFrame = function (pts) {
  3978. videoKeyFrames.push(pts);
  3979. };
  3980. };
  3981. _AudioSegmentStream.prototype = new stream();
  3982. /**
  3983. * Store FlvTags for the h264 stream
  3984. * @param track {object} track metadata configuration
  3985. */
  3986. _VideoSegmentStream = function VideoSegmentStream(track) {
  3987. var nalUnits = [],
  3988. config,
  3989. h264Frame;
  3990. _VideoSegmentStream.prototype.init.call(this);
  3991. this.finishFrame = function (tags, frame) {
  3992. if (!frame) {
  3993. return;
  3994. } // Check if keyframe and the length of tags.
  3995. // This makes sure we write metadata on the first frame of a segment.
  3996. if (config && track && track.newMetadata && (frame.keyFrame || tags.length === 0)) {
  3997. // Push extra data on every IDR frame in case we did a stream change + seek
  3998. var metaTag = metaDataTag(config, frame.dts).finalize();
  3999. var extraTag = extraDataTag(track, frame.dts).finalize();
  4000. metaTag.metaDataTag = extraTag.metaDataTag = true;
  4001. tags.push(metaTag);
  4002. tags.push(extraTag);
  4003. track.newMetadata = false;
  4004. this.trigger('keyframe', frame.dts);
  4005. }
  4006. frame.endNalUnit();
  4007. tags.push(frame.finalize());
  4008. h264Frame = null;
  4009. };
  4010. this.push = function (data) {
  4011. collectTimelineInfo(track, data);
  4012. data.pts = Math.round(data.pts / 90);
  4013. data.dts = Math.round(data.dts / 90); // buffer video until flush() is called
  4014. nalUnits.push(data);
  4015. };
  4016. this.flush = function () {
  4017. var currentNal,
  4018. tags = new tagList(); // Throw away nalUnits at the start of the byte stream until we find
  4019. // the first AUD
  4020. while (nalUnits.length) {
  4021. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  4022. break;
  4023. }
  4024. nalUnits.shift();
  4025. } // return early if no video data has been observed
  4026. if (nalUnits.length === 0) {
  4027. this.trigger('done', 'VideoSegmentStream');
  4028. return;
  4029. }
  4030. while (nalUnits.length) {
  4031. currentNal = nalUnits.shift(); // record the track config
  4032. if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') {
  4033. track.newMetadata = true;
  4034. config = currentNal.config;
  4035. track.width = config.width;
  4036. track.height = config.height;
  4037. track.sps = [currentNal.data];
  4038. track.profileIdc = config.profileIdc;
  4039. track.levelIdc = config.levelIdc;
  4040. track.profileCompatibility = config.profileCompatibility;
  4041. h264Frame.endNalUnit();
  4042. } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') {
  4043. track.newMetadata = true;
  4044. track.pps = [currentNal.data];
  4045. h264Frame.endNalUnit();
  4046. } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  4047. if (h264Frame) {
  4048. this.finishFrame(tags, h264Frame);
  4049. }
  4050. h264Frame = new flvTag(flvTag.VIDEO_TAG);
  4051. h264Frame.pts = currentNal.pts;
  4052. h264Frame.dts = currentNal.dts;
  4053. } else {
  4054. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  4055. // the current sample is a key frame
  4056. h264Frame.keyFrame = true;
  4057. }
  4058. h264Frame.endNalUnit();
  4059. }
  4060. h264Frame.startNalUnit();
  4061. h264Frame.writeBytes(currentNal.data);
  4062. }
  4063. if (h264Frame) {
  4064. this.finishFrame(tags, h264Frame);
  4065. }
  4066. this.trigger('data', {
  4067. track: track,
  4068. tags: tags.list
  4069. }); // Continue with the flush process now
  4070. this.trigger('done', 'VideoSegmentStream');
  4071. };
  4072. };
  4073. _VideoSegmentStream.prototype = new stream();
  4074. /**
  4075. * An object that incrementally transmuxes MPEG2 Trasport Stream
  4076. * chunks into an FLV.
  4077. */
  4078. _Transmuxer = function Transmuxer(options) {
  4079. var self = this,
  4080. packetStream,
  4081. parseStream,
  4082. elementaryStream,
  4083. videoTimestampRolloverStream,
  4084. audioTimestampRolloverStream,
  4085. timedMetadataTimestampRolloverStream,
  4086. adtsStream,
  4087. h264Stream,
  4088. videoSegmentStream,
  4089. audioSegmentStream,
  4090. captionStream,
  4091. coalesceStream$1;
  4092. _Transmuxer.prototype.init.call(this);
  4093. options = options || {}; // expose the metadata stream
  4094. this.metadataStream = new m2ts_1.MetadataStream();
  4095. options.metadataStream = this.metadataStream; // set up the parsing pipeline
  4096. packetStream = new m2ts_1.TransportPacketStream();
  4097. parseStream = new m2ts_1.TransportParseStream();
  4098. elementaryStream = new m2ts_1.ElementaryStream();
  4099. videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  4100. audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  4101. timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  4102. adtsStream = new adts();
  4103. h264Stream = new H264Stream();
  4104. coalesceStream$1 = new coalesceStream(options); // disassemble MPEG2-TS packets into elementary streams
  4105. packetStream.pipe(parseStream).pipe(elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  4106. // demux the streams
  4107. elementaryStream.pipe(videoTimestampRolloverStream).pipe(h264Stream);
  4108. elementaryStream.pipe(audioTimestampRolloverStream).pipe(adtsStream);
  4109. elementaryStream.pipe(timedMetadataTimestampRolloverStream).pipe(this.metadataStream).pipe(coalesceStream$1); // if CEA-708 parsing is available, hook up a caption stream
  4110. captionStream = new m2ts_1.CaptionStream(options);
  4111. h264Stream.pipe(captionStream).pipe(coalesceStream$1); // hook up the segment streams once track metadata is delivered
  4112. elementaryStream.on('data', function (data) {
  4113. var i, videoTrack, audioTrack;
  4114. if (data.type === 'metadata') {
  4115. i = data.tracks.length; // scan the tracks listed in the metadata
  4116. while (i--) {
  4117. if (data.tracks[i].type === 'video') {
  4118. videoTrack = data.tracks[i];
  4119. } else if (data.tracks[i].type === 'audio') {
  4120. audioTrack = data.tracks[i];
  4121. }
  4122. } // hook up the video segment stream to the first track with h264 data
  4123. if (videoTrack && !videoSegmentStream) {
  4124. coalesceStream$1.numberOfTracks++;
  4125. videoSegmentStream = new _VideoSegmentStream(videoTrack); // Set up the final part of the video pipeline
  4126. h264Stream.pipe(videoSegmentStream).pipe(coalesceStream$1);
  4127. }
  4128. if (audioTrack && !audioSegmentStream) {
  4129. // hook up the audio segment stream to the first track with aac data
  4130. coalesceStream$1.numberOfTracks++;
  4131. audioSegmentStream = new _AudioSegmentStream(audioTrack); // Set up the final part of the audio pipeline
  4132. adtsStream.pipe(audioSegmentStream).pipe(coalesceStream$1);
  4133. if (videoSegmentStream) {
  4134. videoSegmentStream.on('keyframe', audioSegmentStream.onVideoKeyFrame);
  4135. }
  4136. }
  4137. }
  4138. }); // feed incoming data to the front of the parsing pipeline
  4139. this.push = function (data) {
  4140. packetStream.push(data);
  4141. }; // flush any buffered data
  4142. this.flush = function () {
  4143. // Start at the top of the pipeline and flush all pending work
  4144. packetStream.flush();
  4145. }; // Caption data has to be reset when seeking outside buffered range
  4146. this.resetCaptions = function () {
  4147. captionStream.reset();
  4148. }; // Re-emit any data coming from the coalesce stream to the outside world
  4149. coalesceStream$1.on('data', function (event) {
  4150. self.trigger('data', event);
  4151. }); // Let the consumer know we have finished flushing the entire pipeline
  4152. coalesceStream$1.on('done', function () {
  4153. self.trigger('done');
  4154. });
  4155. };
  4156. _Transmuxer.prototype = new stream(); // forward compatibility
  4157. var transmuxer = _Transmuxer;
  4158. // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf.
  4159. // Technically, this function returns the header and a metadata FLV tag
  4160. // if duration is greater than zero
  4161. // duration in seconds
  4162. // @return {object} the bytes of the FLV header as a Uint8Array
  4163. var getFlvHeader = function getFlvHeader(duration, audio, video) {
  4164. // :ByteArray {
  4165. var headBytes = new Uint8Array(3 + 1 + 1 + 4),
  4166. head = new DataView(headBytes.buffer),
  4167. metadata,
  4168. result,
  4169. metadataLength; // default arguments
  4170. duration = duration || 0;
  4171. audio = audio === undefined ? true : audio;
  4172. video = video === undefined ? true : video; // signature
  4173. head.setUint8(0, 0x46); // 'F'
  4174. head.setUint8(1, 0x4c); // 'L'
  4175. head.setUint8(2, 0x56); // 'V'
  4176. // version
  4177. head.setUint8(3, 0x01); // flags
  4178. head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00)); // data offset, should be 9 for FLV v1
  4179. head.setUint32(5, headBytes.byteLength); // init the first FLV tag
  4180. if (duration <= 0) {
  4181. // no duration available so just write the first field of the first
  4182. // FLV tag
  4183. result = new Uint8Array(headBytes.byteLength + 4);
  4184. result.set(headBytes);
  4185. result.set([0, 0, 0, 0], headBytes.byteLength);
  4186. return result;
  4187. } // write out the duration metadata tag
  4188. metadata = new flvTag(flvTag.METADATA_TAG);
  4189. metadata.pts = metadata.dts = 0;
  4190. metadata.writeMetaDataDouble('duration', duration);
  4191. metadataLength = metadata.finalize().length;
  4192. result = new Uint8Array(headBytes.byteLength + metadataLength);
  4193. result.set(headBytes);
  4194. result.set(head.byteLength, metadataLength);
  4195. return result;
  4196. };
  4197. var flvHeader = getFlvHeader;
  4198. /**
  4199. * mux.js
  4200. *
  4201. * Copyright (c) Brightcove
  4202. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  4203. */
  4204. var flv = {
  4205. tag: flvTag,
  4206. Transmuxer: transmuxer,
  4207. getFlvHeader: flvHeader
  4208. };
  4209. return flv;
  4210. })));