mux-flv.js 161 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293
  1. /*! @name mux.js @version 7.0.2 @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; // Converts an array of bytes to a unicode hex string.
  1144. function toHexString(byteArray) {
  1145. return byteArray.map(function (byte) {
  1146. return ('0' + (byte & 0xFF).toString(16)).slice(-2);
  1147. }).join('');
  1148. }
  1149. if (isMultiByte) {
  1150. charCodeArray = [currentByte, nextByte];
  1151. i++;
  1152. } else {
  1153. charCodeArray = [currentByte];
  1154. } // Use the TextDecoder if one was created for this service
  1155. if (service.textDecoder_ && !isExtended) {
  1156. char = service.textDecoder_.decode(new Uint8Array(charCodeArray));
  1157. } else {
  1158. // We assume any multi-byte char without a decoder is unicode.
  1159. if (isMultiByte) {
  1160. var unicode = toHexString(charCodeArray); // Takes a unicode hex string and creates a single character.
  1161. char = String.fromCharCode(parseInt(unicode, 16));
  1162. } else {
  1163. char = get708CharFromCode(extended | currentByte);
  1164. }
  1165. }
  1166. if (win.pendingNewLine && !win.isEmpty()) {
  1167. win.newLine(this.getPts(i));
  1168. }
  1169. win.pendingNewLine = false;
  1170. win.addText(char);
  1171. return i;
  1172. };
  1173. /**
  1174. * Handle decoding of multibyte character
  1175. *
  1176. * @param {Integer} i Current index in the 708 packet
  1177. * @param {Service} service The service object to be affected
  1178. * @return {Integer} New index after parsing
  1179. */
  1180. Cea708Stream.prototype.multiByteCharacter = function (i, service) {
  1181. var packetData = this.current708Packet.data;
  1182. var firstByte = packetData[i + 1];
  1183. var secondByte = packetData[i + 2];
  1184. if (within708TextBlock(firstByte) && within708TextBlock(secondByte)) {
  1185. i = this.handleText(++i, service, {
  1186. isMultiByte: true
  1187. });
  1188. }
  1189. return i;
  1190. };
  1191. /**
  1192. * Parse and execute the CW# command.
  1193. *
  1194. * Set the current window.
  1195. *
  1196. * @param {Integer} i Current index in the 708 packet
  1197. * @param {Service} service The service object to be affected
  1198. * @return {Integer} New index after parsing
  1199. */
  1200. Cea708Stream.prototype.setCurrentWindow = function (i, service) {
  1201. var packetData = this.current708Packet.data;
  1202. var b = packetData[i];
  1203. var windowNum = b & 0x07;
  1204. service.setCurrentWindow(windowNum);
  1205. return i;
  1206. };
  1207. /**
  1208. * Parse and execute the DF# command.
  1209. *
  1210. * Define a window and set it as the current window.
  1211. *
  1212. * @param {Integer} i Current index in the 708 packet
  1213. * @param {Service} service The service object to be affected
  1214. * @return {Integer} New index after parsing
  1215. */
  1216. Cea708Stream.prototype.defineWindow = function (i, service) {
  1217. var packetData = this.current708Packet.data;
  1218. var b = packetData[i];
  1219. var windowNum = b & 0x07;
  1220. service.setCurrentWindow(windowNum);
  1221. var win = service.currentWindow;
  1222. b = packetData[++i];
  1223. win.visible = (b & 0x20) >> 5; // v
  1224. win.rowLock = (b & 0x10) >> 4; // rl
  1225. win.columnLock = (b & 0x08) >> 3; // cl
  1226. win.priority = b & 0x07; // p
  1227. b = packetData[++i];
  1228. win.relativePositioning = (b & 0x80) >> 7; // rp
  1229. win.anchorVertical = b & 0x7f; // av
  1230. b = packetData[++i];
  1231. win.anchorHorizontal = b; // ah
  1232. b = packetData[++i];
  1233. win.anchorPoint = (b & 0xf0) >> 4; // ap
  1234. win.rowCount = b & 0x0f; // rc
  1235. b = packetData[++i];
  1236. win.columnCount = b & 0x3f; // cc
  1237. b = packetData[++i];
  1238. win.windowStyle = (b & 0x38) >> 3; // ws
  1239. win.penStyle = b & 0x07; // ps
  1240. // The spec says there are (rowCount+1) "virtual rows"
  1241. win.virtualRowCount = win.rowCount + 1;
  1242. return i;
  1243. };
  1244. /**
  1245. * Parse and execute the SWA command.
  1246. *
  1247. * Set attributes of the current window.
  1248. *
  1249. * @param {Integer} i Current index in the 708 packet
  1250. * @param {Service} service The service object to be affected
  1251. * @return {Integer} New index after parsing
  1252. */
  1253. Cea708Stream.prototype.setWindowAttributes = function (i, service) {
  1254. var packetData = this.current708Packet.data;
  1255. var b = packetData[i];
  1256. var winAttr = service.currentWindow.winAttr;
  1257. b = packetData[++i];
  1258. winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
  1259. winAttr.fillRed = (b & 0x30) >> 4; // fr
  1260. winAttr.fillGreen = (b & 0x0c) >> 2; // fg
  1261. winAttr.fillBlue = b & 0x03; // fb
  1262. b = packetData[++i];
  1263. winAttr.borderType = (b & 0xc0) >> 6; // bt
  1264. winAttr.borderRed = (b & 0x30) >> 4; // br
  1265. winAttr.borderGreen = (b & 0x0c) >> 2; // bg
  1266. winAttr.borderBlue = b & 0x03; // bb
  1267. b = packetData[++i];
  1268. winAttr.borderType += (b & 0x80) >> 5; // bt
  1269. winAttr.wordWrap = (b & 0x40) >> 6; // ww
  1270. winAttr.printDirection = (b & 0x30) >> 4; // pd
  1271. winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
  1272. winAttr.justify = b & 0x03; // j
  1273. b = packetData[++i];
  1274. winAttr.effectSpeed = (b & 0xf0) >> 4; // es
  1275. winAttr.effectDirection = (b & 0x0c) >> 2; // ed
  1276. winAttr.displayEffect = b & 0x03; // de
  1277. return i;
  1278. };
  1279. /**
  1280. * Gather text from all displayed windows and push a caption to output.
  1281. *
  1282. * @param {Integer} i Current index in the 708 packet
  1283. * @param {Service} service The service object to be affected
  1284. */
  1285. Cea708Stream.prototype.flushDisplayed = function (pts, service) {
  1286. var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
  1287. // display text in the correct order, but sample files so far have not shown any issue.
  1288. for (var winId = 0; winId < 8; winId++) {
  1289. if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
  1290. displayedText.push(service.windows[winId].getText());
  1291. }
  1292. }
  1293. service.endPts = pts;
  1294. service.text = displayedText.join('\n\n');
  1295. this.pushCaption(service);
  1296. service.startPts = pts;
  1297. };
  1298. /**
  1299. * Push a caption to output if the caption contains text.
  1300. *
  1301. * @param {Service} service The service object to be affected
  1302. */
  1303. Cea708Stream.prototype.pushCaption = function (service) {
  1304. if (service.text !== '') {
  1305. this.trigger('data', {
  1306. startPts: service.startPts,
  1307. endPts: service.endPts,
  1308. text: service.text,
  1309. stream: 'cc708_' + service.serviceNum
  1310. });
  1311. service.text = '';
  1312. service.startPts = service.endPts;
  1313. }
  1314. };
  1315. /**
  1316. * Parse and execute the DSW command.
  1317. *
  1318. * Set visible property of windows based on the parsed bitmask.
  1319. *
  1320. * @param {Integer} i Current index in the 708 packet
  1321. * @param {Service} service The service object to be affected
  1322. * @return {Integer} New index after parsing
  1323. */
  1324. Cea708Stream.prototype.displayWindows = function (i, service) {
  1325. var packetData = this.current708Packet.data;
  1326. var b = packetData[++i];
  1327. var pts = this.getPts(i);
  1328. this.flushDisplayed(pts, service);
  1329. for (var winId = 0; winId < 8; winId++) {
  1330. if (b & 0x01 << winId) {
  1331. service.windows[winId].visible = 1;
  1332. }
  1333. }
  1334. return i;
  1335. };
  1336. /**
  1337. * Parse and execute the HDW command.
  1338. *
  1339. * Set visible property of windows based on the parsed bitmask.
  1340. *
  1341. * @param {Integer} i Current index in the 708 packet
  1342. * @param {Service} service The service object to be affected
  1343. * @return {Integer} New index after parsing
  1344. */
  1345. Cea708Stream.prototype.hideWindows = function (i, service) {
  1346. var packetData = this.current708Packet.data;
  1347. var b = packetData[++i];
  1348. var pts = this.getPts(i);
  1349. this.flushDisplayed(pts, service);
  1350. for (var winId = 0; winId < 8; winId++) {
  1351. if (b & 0x01 << winId) {
  1352. service.windows[winId].visible = 0;
  1353. }
  1354. }
  1355. return i;
  1356. };
  1357. /**
  1358. * Parse and execute the TGW command.
  1359. *
  1360. * Set visible property of windows based on the parsed bitmask.
  1361. *
  1362. * @param {Integer} i Current index in the 708 packet
  1363. * @param {Service} service The service object to be affected
  1364. * @return {Integer} New index after parsing
  1365. */
  1366. Cea708Stream.prototype.toggleWindows = function (i, service) {
  1367. var packetData = this.current708Packet.data;
  1368. var b = packetData[++i];
  1369. var pts = this.getPts(i);
  1370. this.flushDisplayed(pts, service);
  1371. for (var winId = 0; winId < 8; winId++) {
  1372. if (b & 0x01 << winId) {
  1373. service.windows[winId].visible ^= 1;
  1374. }
  1375. }
  1376. return i;
  1377. };
  1378. /**
  1379. * Parse and execute the CLW command.
  1380. *
  1381. * Clear text of windows based on the parsed bitmask.
  1382. *
  1383. * @param {Integer} i Current index in the 708 packet
  1384. * @param {Service} service The service object to be affected
  1385. * @return {Integer} New index after parsing
  1386. */
  1387. Cea708Stream.prototype.clearWindows = function (i, service) {
  1388. var packetData = this.current708Packet.data;
  1389. var b = packetData[++i];
  1390. var pts = this.getPts(i);
  1391. this.flushDisplayed(pts, service);
  1392. for (var winId = 0; winId < 8; winId++) {
  1393. if (b & 0x01 << winId) {
  1394. service.windows[winId].clearText();
  1395. }
  1396. }
  1397. return i;
  1398. };
  1399. /**
  1400. * Parse and execute the DLW command.
  1401. *
  1402. * Re-initialize windows based on the parsed bitmask.
  1403. *
  1404. * @param {Integer} i Current index in the 708 packet
  1405. * @param {Service} service The service object to be affected
  1406. * @return {Integer} New index after parsing
  1407. */
  1408. Cea708Stream.prototype.deleteWindows = function (i, service) {
  1409. var packetData = this.current708Packet.data;
  1410. var b = packetData[++i];
  1411. var pts = this.getPts(i);
  1412. this.flushDisplayed(pts, service);
  1413. for (var winId = 0; winId < 8; winId++) {
  1414. if (b & 0x01 << winId) {
  1415. service.windows[winId].reset();
  1416. }
  1417. }
  1418. return i;
  1419. };
  1420. /**
  1421. * Parse and execute the SPA command.
  1422. *
  1423. * Set pen attributes of the current window.
  1424. *
  1425. * @param {Integer} i Current index in the 708 packet
  1426. * @param {Service} service The service object to be affected
  1427. * @return {Integer} New index after parsing
  1428. */
  1429. Cea708Stream.prototype.setPenAttributes = function (i, service) {
  1430. var packetData = this.current708Packet.data;
  1431. var b = packetData[i];
  1432. var penAttr = service.currentWindow.penAttr;
  1433. b = packetData[++i];
  1434. penAttr.textTag = (b & 0xf0) >> 4; // tt
  1435. penAttr.offset = (b & 0x0c) >> 2; // o
  1436. penAttr.penSize = b & 0x03; // s
  1437. b = packetData[++i];
  1438. penAttr.italics = (b & 0x80) >> 7; // i
  1439. penAttr.underline = (b & 0x40) >> 6; // u
  1440. penAttr.edgeType = (b & 0x38) >> 3; // et
  1441. penAttr.fontStyle = b & 0x07; // fs
  1442. return i;
  1443. };
  1444. /**
  1445. * Parse and execute the SPC command.
  1446. *
  1447. * Set pen color of the current window.
  1448. *
  1449. * @param {Integer} i Current index in the 708 packet
  1450. * @param {Service} service The service object to be affected
  1451. * @return {Integer} New index after parsing
  1452. */
  1453. Cea708Stream.prototype.setPenColor = function (i, service) {
  1454. var packetData = this.current708Packet.data;
  1455. var b = packetData[i];
  1456. var penColor = service.currentWindow.penColor;
  1457. b = packetData[++i];
  1458. penColor.fgOpacity = (b & 0xc0) >> 6; // fo
  1459. penColor.fgRed = (b & 0x30) >> 4; // fr
  1460. penColor.fgGreen = (b & 0x0c) >> 2; // fg
  1461. penColor.fgBlue = b & 0x03; // fb
  1462. b = packetData[++i];
  1463. penColor.bgOpacity = (b & 0xc0) >> 6; // bo
  1464. penColor.bgRed = (b & 0x30) >> 4; // br
  1465. penColor.bgGreen = (b & 0x0c) >> 2; // bg
  1466. penColor.bgBlue = b & 0x03; // bb
  1467. b = packetData[++i];
  1468. penColor.edgeRed = (b & 0x30) >> 4; // er
  1469. penColor.edgeGreen = (b & 0x0c) >> 2; // eg
  1470. penColor.edgeBlue = b & 0x03; // eb
  1471. return i;
  1472. };
  1473. /**
  1474. * Parse and execute the SPL command.
  1475. *
  1476. * Set pen location of the current window.
  1477. *
  1478. * @param {Integer} i Current index in the 708 packet
  1479. * @param {Service} service The service object to be affected
  1480. * @return {Integer} New index after parsing
  1481. */
  1482. Cea708Stream.prototype.setPenLocation = function (i, service) {
  1483. var packetData = this.current708Packet.data;
  1484. var b = packetData[i];
  1485. var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
  1486. service.currentWindow.pendingNewLine = true;
  1487. b = packetData[++i];
  1488. penLoc.row = b & 0x0f; // r
  1489. b = packetData[++i];
  1490. penLoc.column = b & 0x3f; // c
  1491. return i;
  1492. };
  1493. /**
  1494. * Execute the RST command.
  1495. *
  1496. * Reset service to a clean slate. Re-initialize.
  1497. *
  1498. * @param {Integer} i Current index in the 708 packet
  1499. * @param {Service} service The service object to be affected
  1500. * @return {Service} Re-initialized service
  1501. */
  1502. Cea708Stream.prototype.reset = function (i, service) {
  1503. var pts = this.getPts(i);
  1504. this.flushDisplayed(pts, service);
  1505. return this.initService(service.serviceNum, i);
  1506. }; // This hash maps non-ASCII, special, and extended character codes to their
  1507. // proper Unicode equivalent. The first keys that are only a single byte
  1508. // are the non-standard ASCII characters, which simply map the CEA608 byte
  1509. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  1510. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  1511. // can be performed regardless of the field and data channel on which the
  1512. // character code was received.
  1513. var CHARACTER_TRANSLATION = {
  1514. 0x2a: 0xe1,
  1515. // á
  1516. 0x5c: 0xe9,
  1517. // é
  1518. 0x5e: 0xed,
  1519. // í
  1520. 0x5f: 0xf3,
  1521. // ó
  1522. 0x60: 0xfa,
  1523. // ú
  1524. 0x7b: 0xe7,
  1525. // ç
  1526. 0x7c: 0xf7,
  1527. // ÷
  1528. 0x7d: 0xd1,
  1529. // Ñ
  1530. 0x7e: 0xf1,
  1531. // ñ
  1532. 0x7f: 0x2588,
  1533. // █
  1534. 0x0130: 0xae,
  1535. // ®
  1536. 0x0131: 0xb0,
  1537. // °
  1538. 0x0132: 0xbd,
  1539. // ½
  1540. 0x0133: 0xbf,
  1541. // ¿
  1542. 0x0134: 0x2122,
  1543. // ™
  1544. 0x0135: 0xa2,
  1545. // ¢
  1546. 0x0136: 0xa3,
  1547. // £
  1548. 0x0137: 0x266a,
  1549. // ♪
  1550. 0x0138: 0xe0,
  1551. // à
  1552. 0x0139: 0xa0,
  1553. //
  1554. 0x013a: 0xe8,
  1555. // è
  1556. 0x013b: 0xe2,
  1557. // â
  1558. 0x013c: 0xea,
  1559. // ê
  1560. 0x013d: 0xee,
  1561. // î
  1562. 0x013e: 0xf4,
  1563. // ô
  1564. 0x013f: 0xfb,
  1565. // û
  1566. 0x0220: 0xc1,
  1567. // Á
  1568. 0x0221: 0xc9,
  1569. // É
  1570. 0x0222: 0xd3,
  1571. // Ó
  1572. 0x0223: 0xda,
  1573. // Ú
  1574. 0x0224: 0xdc,
  1575. // Ü
  1576. 0x0225: 0xfc,
  1577. // ü
  1578. 0x0226: 0x2018,
  1579. // ‘
  1580. 0x0227: 0xa1,
  1581. // ¡
  1582. 0x0228: 0x2a,
  1583. // *
  1584. 0x0229: 0x27,
  1585. // '
  1586. 0x022a: 0x2014,
  1587. // —
  1588. 0x022b: 0xa9,
  1589. // ©
  1590. 0x022c: 0x2120,
  1591. // ℠
  1592. 0x022d: 0x2022,
  1593. // •
  1594. 0x022e: 0x201c,
  1595. // “
  1596. 0x022f: 0x201d,
  1597. // ”
  1598. 0x0230: 0xc0,
  1599. // À
  1600. 0x0231: 0xc2,
  1601. // Â
  1602. 0x0232: 0xc7,
  1603. // Ç
  1604. 0x0233: 0xc8,
  1605. // È
  1606. 0x0234: 0xca,
  1607. // Ê
  1608. 0x0235: 0xcb,
  1609. // Ë
  1610. 0x0236: 0xeb,
  1611. // ë
  1612. 0x0237: 0xce,
  1613. // Î
  1614. 0x0238: 0xcf,
  1615. // Ï
  1616. 0x0239: 0xef,
  1617. // ï
  1618. 0x023a: 0xd4,
  1619. // Ô
  1620. 0x023b: 0xd9,
  1621. // Ù
  1622. 0x023c: 0xf9,
  1623. // ù
  1624. 0x023d: 0xdb,
  1625. // Û
  1626. 0x023e: 0xab,
  1627. // «
  1628. 0x023f: 0xbb,
  1629. // »
  1630. 0x0320: 0xc3,
  1631. // Ã
  1632. 0x0321: 0xe3,
  1633. // ã
  1634. 0x0322: 0xcd,
  1635. // Í
  1636. 0x0323: 0xcc,
  1637. // Ì
  1638. 0x0324: 0xec,
  1639. // ì
  1640. 0x0325: 0xd2,
  1641. // Ò
  1642. 0x0326: 0xf2,
  1643. // ò
  1644. 0x0327: 0xd5,
  1645. // Õ
  1646. 0x0328: 0xf5,
  1647. // õ
  1648. 0x0329: 0x7b,
  1649. // {
  1650. 0x032a: 0x7d,
  1651. // }
  1652. 0x032b: 0x5c,
  1653. // \
  1654. 0x032c: 0x5e,
  1655. // ^
  1656. 0x032d: 0x5f,
  1657. // _
  1658. 0x032e: 0x7c,
  1659. // |
  1660. 0x032f: 0x7e,
  1661. // ~
  1662. 0x0330: 0xc4,
  1663. // Ä
  1664. 0x0331: 0xe4,
  1665. // ä
  1666. 0x0332: 0xd6,
  1667. // Ö
  1668. 0x0333: 0xf6,
  1669. // ö
  1670. 0x0334: 0xdf,
  1671. // ß
  1672. 0x0335: 0xa5,
  1673. // ¥
  1674. 0x0336: 0xa4,
  1675. // ¤
  1676. 0x0337: 0x2502,
  1677. // │
  1678. 0x0338: 0xc5,
  1679. // Å
  1680. 0x0339: 0xe5,
  1681. // å
  1682. 0x033a: 0xd8,
  1683. // Ø
  1684. 0x033b: 0xf8,
  1685. // ø
  1686. 0x033c: 0x250c,
  1687. // ┌
  1688. 0x033d: 0x2510,
  1689. // ┐
  1690. 0x033e: 0x2514,
  1691. // └
  1692. 0x033f: 0x2518 // ┘
  1693. };
  1694. var getCharFromCode = function getCharFromCode(code) {
  1695. if (code === null) {
  1696. return '';
  1697. }
  1698. code = CHARACTER_TRANSLATION[code] || code;
  1699. return String.fromCharCode(code);
  1700. }; // the index of the last row in a CEA-608 display buffer
  1701. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  1702. // getting it through bit logic.
  1703. 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
  1704. // cells. The "bottom" row is the last element in the outer array.
  1705. // We keep track of positioning information as we go by storing the
  1706. // number of indentations and the tab offset in this buffer.
  1707. var createDisplayBuffer = function createDisplayBuffer() {
  1708. var result = [],
  1709. i = BOTTOM_ROW + 1;
  1710. while (i--) {
  1711. result.push({
  1712. text: '',
  1713. indent: 0,
  1714. offset: 0
  1715. });
  1716. }
  1717. return result;
  1718. };
  1719. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  1720. Cea608Stream.prototype.init.call(this);
  1721. this.field_ = field || 0;
  1722. this.dataChannel_ = dataChannel || 0;
  1723. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  1724. this.setConstants();
  1725. this.reset();
  1726. this.push = function (packet) {
  1727. var data, swap, char0, char1, text; // remove the parity bits
  1728. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  1729. if (data === this.lastControlCode_) {
  1730. this.lastControlCode_ = null;
  1731. return;
  1732. } // Store control codes
  1733. if ((data & 0xf000) === 0x1000) {
  1734. this.lastControlCode_ = data;
  1735. } else if (data !== this.PADDING_) {
  1736. this.lastControlCode_ = null;
  1737. }
  1738. char0 = data >>> 8;
  1739. char1 = data & 0xff;
  1740. if (data === this.PADDING_) {
  1741. return;
  1742. } else if (data === this.RESUME_CAPTION_LOADING_) {
  1743. this.mode_ = 'popOn';
  1744. } else if (data === this.END_OF_CAPTION_) {
  1745. // If an EOC is received while in paint-on mode, the displayed caption
  1746. // text should be swapped to non-displayed memory as if it was a pop-on
  1747. // caption. Because of that, we should explicitly switch back to pop-on
  1748. // mode
  1749. this.mode_ = 'popOn';
  1750. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  1751. this.flushDisplayed(packet.pts); // flip memory
  1752. swap = this.displayed_;
  1753. this.displayed_ = this.nonDisplayed_;
  1754. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  1755. this.startPts_ = packet.pts;
  1756. } else if (data === this.ROLL_UP_2_ROWS_) {
  1757. this.rollUpRows_ = 2;
  1758. this.setRollUp(packet.pts);
  1759. } else if (data === this.ROLL_UP_3_ROWS_) {
  1760. this.rollUpRows_ = 3;
  1761. this.setRollUp(packet.pts);
  1762. } else if (data === this.ROLL_UP_4_ROWS_) {
  1763. this.rollUpRows_ = 4;
  1764. this.setRollUp(packet.pts);
  1765. } else if (data === this.CARRIAGE_RETURN_) {
  1766. this.clearFormatting(packet.pts);
  1767. this.flushDisplayed(packet.pts);
  1768. this.shiftRowsUp_();
  1769. this.startPts_ = packet.pts;
  1770. } else if (data === this.BACKSPACE_) {
  1771. if (this.mode_ === 'popOn') {
  1772. this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
  1773. } else {
  1774. this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
  1775. }
  1776. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  1777. this.flushDisplayed(packet.pts);
  1778. this.displayed_ = createDisplayBuffer();
  1779. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  1780. this.nonDisplayed_ = createDisplayBuffer();
  1781. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  1782. if (this.mode_ !== 'paintOn') {
  1783. // NOTE: This should be removed when proper caption positioning is
  1784. // implemented
  1785. this.flushDisplayed(packet.pts);
  1786. this.displayed_ = createDisplayBuffer();
  1787. }
  1788. this.mode_ = 'paintOn';
  1789. this.startPts_ = packet.pts; // Append special characters to caption text
  1790. } else if (this.isSpecialCharacter(char0, char1)) {
  1791. // Bitmask char0 so that we can apply character transformations
  1792. // regardless of field and data channel.
  1793. // Then byte-shift to the left and OR with char1 so we can pass the
  1794. // entire character code to `getCharFromCode`.
  1795. char0 = (char0 & 0x03) << 8;
  1796. text = getCharFromCode(char0 | char1);
  1797. this[this.mode_](packet.pts, text);
  1798. this.column_++; // Append extended characters to caption text
  1799. } else if (this.isExtCharacter(char0, char1)) {
  1800. // Extended characters always follow their "non-extended" equivalents.
  1801. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  1802. // decoders are supposed to drop the "è", while compliant decoders
  1803. // backspace the "e" and insert "è".
  1804. // Delete the previous character
  1805. if (this.mode_ === 'popOn') {
  1806. this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
  1807. } else {
  1808. this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
  1809. } // Bitmask char0 so that we can apply character transformations
  1810. // regardless of field and data channel.
  1811. // Then byte-shift to the left and OR with char1 so we can pass the
  1812. // entire character code to `getCharFromCode`.
  1813. char0 = (char0 & 0x03) << 8;
  1814. text = getCharFromCode(char0 | char1);
  1815. this[this.mode_](packet.pts, text);
  1816. this.column_++; // Process mid-row codes
  1817. } else if (this.isMidRowCode(char0, char1)) {
  1818. // Attributes are not additive, so clear all formatting
  1819. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  1820. // should be replaced with spaces, so add one now
  1821. this[this.mode_](packet.pts, ' ');
  1822. this.column_++;
  1823. if ((char1 & 0xe) === 0xe) {
  1824. this.addFormatting(packet.pts, ['i']);
  1825. }
  1826. if ((char1 & 0x1) === 0x1) {
  1827. this.addFormatting(packet.pts, ['u']);
  1828. } // Detect offset control codes and adjust cursor
  1829. } else if (this.isOffsetControlCode(char0, char1)) {
  1830. // Cursor position is set by indent PAC (see below) in 4-column
  1831. // increments, with an additional offset code of 1-3 to reach any
  1832. // of the 32 columns specified by CEA-608. So all we need to do
  1833. // here is increment the column cursor by the given offset.
  1834. var offset = char1 & 0x03; // For an offest value 1-3, set the offset for that caption
  1835. // in the non-displayed array.
  1836. this.nonDisplayed_[this.row_].offset = offset;
  1837. this.column_ += offset; // Detect PACs (Preamble Address Codes)
  1838. } else if (this.isPAC(char0, char1)) {
  1839. // There's no logic for PAC -> row mapping, so we have to just
  1840. // find the row code in an array and use its index :(
  1841. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  1842. if (this.mode_ === 'rollUp') {
  1843. // This implies that the base row is incorrectly set.
  1844. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  1845. // of roll-up rows set.
  1846. if (row - this.rollUpRows_ + 1 < 0) {
  1847. row = this.rollUpRows_ - 1;
  1848. }
  1849. this.setRollUp(packet.pts, row);
  1850. }
  1851. if (row !== this.row_) {
  1852. // formatting is only persistent for current row
  1853. this.clearFormatting(packet.pts);
  1854. this.row_ = row;
  1855. } // All PACs can apply underline, so detect and apply
  1856. // (All odd-numbered second bytes set underline)
  1857. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  1858. this.addFormatting(packet.pts, ['u']);
  1859. }
  1860. if ((data & 0x10) === 0x10) {
  1861. // We've got an indent level code. Each successive even number
  1862. // increments the column cursor by 4, so we can get the desired
  1863. // column position by bit-shifting to the right (to get n/2)
  1864. // and multiplying by 4.
  1865. var indentations = (data & 0xe) >> 1;
  1866. this.column_ = indentations * 4; // add to the number of indentations for positioning
  1867. this.nonDisplayed_[this.row_].indent += indentations;
  1868. }
  1869. if (this.isColorPAC(char1)) {
  1870. // it's a color code, though we only support white, which
  1871. // can be either normal or italicized. white italics can be
  1872. // either 0x4e or 0x6e depending on the row, so we just
  1873. // bitwise-and with 0xe to see if italics should be turned on
  1874. if ((char1 & 0xe) === 0xe) {
  1875. this.addFormatting(packet.pts, ['i']);
  1876. }
  1877. } // We have a normal character in char0, and possibly one in char1
  1878. } else if (this.isNormalChar(char0)) {
  1879. if (char1 === 0x00) {
  1880. char1 = null;
  1881. }
  1882. text = getCharFromCode(char0);
  1883. text += getCharFromCode(char1);
  1884. this[this.mode_](packet.pts, text);
  1885. this.column_ += text.length;
  1886. } // finish data processing
  1887. };
  1888. };
  1889. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  1890. // display buffer
  1891. Cea608Stream.prototype.flushDisplayed = function (pts) {
  1892. var _this = this;
  1893. var logWarning = function logWarning(index) {
  1894. _this.trigger('log', {
  1895. level: 'warn',
  1896. message: 'Skipping a malformed 608 caption at index ' + index + '.'
  1897. });
  1898. };
  1899. var content = [];
  1900. this.displayed_.forEach(function (row, i) {
  1901. if (row && row.text && row.text.length) {
  1902. try {
  1903. // remove spaces from the start and end of the string
  1904. row.text = row.text.trim();
  1905. } catch (e) {
  1906. // Ordinarily, this shouldn't happen. However, caption
  1907. // parsing errors should not throw exceptions and
  1908. // break playback.
  1909. logWarning(i);
  1910. } // See the below link for more details on the following fields:
  1911. // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
  1912. if (row.text.length) {
  1913. content.push({
  1914. // The text to be displayed in the caption from this specific row, with whitespace removed.
  1915. text: row.text,
  1916. // Value between 1 and 15 representing the PAC row used to calculate line height.
  1917. line: i + 1,
  1918. // A number representing the indent position by percentage (CEA-608 PAC indent code).
  1919. // The value will be a number between 10 and 80. Offset is used to add an aditional
  1920. // value to the position if necessary.
  1921. position: 10 + Math.min(70, row.indent * 10) + row.offset * 2.5
  1922. });
  1923. }
  1924. } else if (row === undefined || row === null) {
  1925. logWarning(i);
  1926. }
  1927. });
  1928. if (content.length) {
  1929. this.trigger('data', {
  1930. startPts: this.startPts_,
  1931. endPts: pts,
  1932. content: content,
  1933. stream: this.name_
  1934. });
  1935. }
  1936. };
  1937. /**
  1938. * Zero out the data, used for startup and on seek
  1939. */
  1940. Cea608Stream.prototype.reset = function () {
  1941. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  1942. // actually display captions. If a caption is shifted to a row
  1943. // with a lower index than this, it is cleared from the display
  1944. // buffer
  1945. this.topRow_ = 0;
  1946. this.startPts_ = 0;
  1947. this.displayed_ = createDisplayBuffer();
  1948. this.nonDisplayed_ = createDisplayBuffer();
  1949. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  1950. this.column_ = 0;
  1951. this.row_ = BOTTOM_ROW;
  1952. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  1953. this.formatting_ = [];
  1954. };
  1955. /**
  1956. * Sets up control code and related constants for this instance
  1957. */
  1958. Cea608Stream.prototype.setConstants = function () {
  1959. // The following attributes have these uses:
  1960. // ext_ : char0 for mid-row codes, and the base for extended
  1961. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  1962. // extended codes)
  1963. // control_: char0 for control codes, except byte-shifted to the
  1964. // left so that we can do this.control_ | CONTROL_CODE
  1965. // offset_: char0 for tab offset codes
  1966. //
  1967. // It's also worth noting that control codes, and _only_ control codes,
  1968. // differ between field 1 and field2. Field 2 control codes are always
  1969. // their field 1 value plus 1. That's why there's the "| field" on the
  1970. // control value.
  1971. if (this.dataChannel_ === 0) {
  1972. this.BASE_ = 0x10;
  1973. this.EXT_ = 0x11;
  1974. this.CONTROL_ = (0x14 | this.field_) << 8;
  1975. this.OFFSET_ = 0x17;
  1976. } else if (this.dataChannel_ === 1) {
  1977. this.BASE_ = 0x18;
  1978. this.EXT_ = 0x19;
  1979. this.CONTROL_ = (0x1c | this.field_) << 8;
  1980. this.OFFSET_ = 0x1f;
  1981. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  1982. // list is not exhaustive. For a more comprehensive listing and semantics see
  1983. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  1984. // Padding
  1985. this.PADDING_ = 0x0000; // Pop-on Mode
  1986. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  1987. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  1988. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  1989. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  1990. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  1991. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  1992. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  1993. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  1994. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  1995. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  1996. };
  1997. /**
  1998. * Detects if the 2-byte packet data is a special character
  1999. *
  2000. * Special characters have a second byte in the range 0x30 to 0x3f,
  2001. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  2002. * data channel 2).
  2003. *
  2004. * @param {Integer} char0 The first byte
  2005. * @param {Integer} char1 The second byte
  2006. * @return {Boolean} Whether the 2 bytes are an special character
  2007. */
  2008. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  2009. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  2010. };
  2011. /**
  2012. * Detects if the 2-byte packet data is an extended character
  2013. *
  2014. * Extended characters have a second byte in the range 0x20 to 0x3f,
  2015. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  2016. * 0x1a or 0x1b (for data channel 2).
  2017. *
  2018. * @param {Integer} char0 The first byte
  2019. * @param {Integer} char1 The second byte
  2020. * @return {Boolean} Whether the 2 bytes are an extended character
  2021. */
  2022. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  2023. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  2024. };
  2025. /**
  2026. * Detects if the 2-byte packet is a mid-row code
  2027. *
  2028. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  2029. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  2030. * channel 2).
  2031. *
  2032. * @param {Integer} char0 The first byte
  2033. * @param {Integer} char1 The second byte
  2034. * @return {Boolean} Whether the 2 bytes are a mid-row code
  2035. */
  2036. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  2037. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  2038. };
  2039. /**
  2040. * Detects if the 2-byte packet is an offset control code
  2041. *
  2042. * Offset control codes have a second byte in the range 0x21 to 0x23,
  2043. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  2044. * data channel 2).
  2045. *
  2046. * @param {Integer} char0 The first byte
  2047. * @param {Integer} char1 The second byte
  2048. * @return {Boolean} Whether the 2 bytes are an offset control code
  2049. */
  2050. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  2051. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  2052. };
  2053. /**
  2054. * Detects if the 2-byte packet is a Preamble Address Code
  2055. *
  2056. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  2057. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  2058. * range 0x40 to 0x7f.
  2059. *
  2060. * @param {Integer} char0 The first byte
  2061. * @param {Integer} char1 The second byte
  2062. * @return {Boolean} Whether the 2 bytes are a PAC
  2063. */
  2064. Cea608Stream.prototype.isPAC = function (char0, char1) {
  2065. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  2066. };
  2067. /**
  2068. * Detects if a packet's second byte is in the range of a PAC color code
  2069. *
  2070. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  2071. * 0x60 to 0x6f.
  2072. *
  2073. * @param {Integer} char1 The second byte
  2074. * @return {Boolean} Whether the byte is a color PAC
  2075. */
  2076. Cea608Stream.prototype.isColorPAC = function (char1) {
  2077. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  2078. };
  2079. /**
  2080. * Detects if a single byte is in the range of a normal character
  2081. *
  2082. * Normal text bytes are in the range 0x20 to 0x7f.
  2083. *
  2084. * @param {Integer} char The byte
  2085. * @return {Boolean} Whether the byte is a normal character
  2086. */
  2087. Cea608Stream.prototype.isNormalChar = function (char) {
  2088. return char >= 0x20 && char <= 0x7f;
  2089. };
  2090. /**
  2091. * Configures roll-up
  2092. *
  2093. * @param {Integer} pts Current PTS
  2094. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  2095. * a new position
  2096. */
  2097. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  2098. // Reset the base row to the bottom row when switching modes
  2099. if (this.mode_ !== 'rollUp') {
  2100. this.row_ = BOTTOM_ROW;
  2101. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  2102. this.flushDisplayed(pts);
  2103. this.nonDisplayed_ = createDisplayBuffer();
  2104. this.displayed_ = createDisplayBuffer();
  2105. }
  2106. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  2107. // move currently displayed captions (up or down) to the new base row
  2108. for (var i = 0; i < this.rollUpRows_; i++) {
  2109. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  2110. this.displayed_[this.row_ - i] = {
  2111. text: '',
  2112. indent: 0,
  2113. offset: 0
  2114. };
  2115. }
  2116. }
  2117. if (newBaseRow === undefined) {
  2118. newBaseRow = this.row_;
  2119. }
  2120. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  2121. }; // Adds the opening HTML tag for the passed character to the caption text,
  2122. // and keeps track of it for later closing
  2123. Cea608Stream.prototype.addFormatting = function (pts, format) {
  2124. this.formatting_ = this.formatting_.concat(format);
  2125. var text = format.reduce(function (text, format) {
  2126. return text + '<' + format + '>';
  2127. }, '');
  2128. this[this.mode_](pts, text);
  2129. }; // Adds HTML closing tags for current formatting to caption text and
  2130. // clears remembered formatting
  2131. Cea608Stream.prototype.clearFormatting = function (pts) {
  2132. if (!this.formatting_.length) {
  2133. return;
  2134. }
  2135. var text = this.formatting_.reverse().reduce(function (text, format) {
  2136. return text + '</' + format + '>';
  2137. }, '');
  2138. this.formatting_ = [];
  2139. this[this.mode_](pts, text);
  2140. }; // Mode Implementations
  2141. Cea608Stream.prototype.popOn = function (pts, text) {
  2142. var baseRow = this.nonDisplayed_[this.row_].text; // buffer characters
  2143. baseRow += text;
  2144. this.nonDisplayed_[this.row_].text = baseRow;
  2145. };
  2146. Cea608Stream.prototype.rollUp = function (pts, text) {
  2147. var baseRow = this.displayed_[this.row_].text;
  2148. baseRow += text;
  2149. this.displayed_[this.row_].text = baseRow;
  2150. };
  2151. Cea608Stream.prototype.shiftRowsUp_ = function () {
  2152. var i; // clear out inactive rows
  2153. for (i = 0; i < this.topRow_; i++) {
  2154. this.displayed_[i] = {
  2155. text: '',
  2156. indent: 0,
  2157. offset: 0
  2158. };
  2159. }
  2160. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  2161. this.displayed_[i] = {
  2162. text: '',
  2163. indent: 0,
  2164. offset: 0
  2165. };
  2166. } // shift displayed rows up
  2167. for (i = this.topRow_; i < this.row_; i++) {
  2168. this.displayed_[i] = this.displayed_[i + 1];
  2169. } // clear out the bottom row
  2170. this.displayed_[this.row_] = {
  2171. text: '',
  2172. indent: 0,
  2173. offset: 0
  2174. };
  2175. };
  2176. Cea608Stream.prototype.paintOn = function (pts, text) {
  2177. var baseRow = this.displayed_[this.row_].text;
  2178. baseRow += text;
  2179. this.displayed_[this.row_].text = baseRow;
  2180. }; // exports
  2181. var captionStream = {
  2182. CaptionStream: CaptionStream,
  2183. Cea608Stream: Cea608Stream,
  2184. Cea708Stream: Cea708Stream
  2185. };
  2186. /**
  2187. * mux.js
  2188. *
  2189. * Copyright (c) Brightcove
  2190. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  2191. */
  2192. var streamTypes = {
  2193. H264_STREAM_TYPE: 0x1B,
  2194. ADTS_STREAM_TYPE: 0x0F,
  2195. METADATA_STREAM_TYPE: 0x15
  2196. };
  2197. var MAX_TS = 8589934592;
  2198. var RO_THRESH = 4294967296;
  2199. var TYPE_SHARED = 'shared';
  2200. var handleRollover = function handleRollover(value, reference) {
  2201. var direction = 1;
  2202. if (value > reference) {
  2203. // If the current timestamp value is greater than our reference timestamp and we detect a
  2204. // timestamp rollover, this means the roll over is happening in the opposite direction.
  2205. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  2206. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  2207. // rollover point. In loading this segment, the timestamp values will be very large,
  2208. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  2209. // the time stamp to be `value - 2^33`.
  2210. direction = -1;
  2211. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  2212. // cause an incorrect adjustment.
  2213. while (Math.abs(reference - value) > RO_THRESH) {
  2214. value += direction * MAX_TS;
  2215. }
  2216. return value;
  2217. };
  2218. var TimestampRolloverStream$1 = function TimestampRolloverStream(type) {
  2219. var lastDTS, referenceDTS;
  2220. TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
  2221. // video and audio. We could use `undefined` here, but having a string
  2222. // makes debugging a little clearer.
  2223. this.type_ = type || TYPE_SHARED;
  2224. this.push = function (data) {
  2225. /**
  2226. * Rollover stream expects data from elementary stream.
  2227. * Elementary stream can push forward 2 types of data
  2228. * - Parsed Video/Audio/Timed-metadata PES (packetized elementary stream) packets
  2229. * - Tracks metadata from PMT (Program Map Table)
  2230. * Rollover stream expects pts/dts info to be available, since it stores lastDTS
  2231. * We should ignore non-PES packets since they may override lastDTS to undefined.
  2232. * lastDTS is important to signal the next segments
  2233. * about rollover from the previous segments.
  2234. */
  2235. if (data.type === 'metadata') {
  2236. this.trigger('data', data);
  2237. return;
  2238. } // Any "shared" rollover streams will accept _all_ data. Otherwise,
  2239. // streams will only accept data that matches their type.
  2240. if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
  2241. return;
  2242. }
  2243. if (referenceDTS === undefined) {
  2244. referenceDTS = data.dts;
  2245. }
  2246. data.dts = handleRollover(data.dts, referenceDTS);
  2247. data.pts = handleRollover(data.pts, referenceDTS);
  2248. lastDTS = data.dts;
  2249. this.trigger('data', data);
  2250. };
  2251. this.flush = function () {
  2252. referenceDTS = lastDTS;
  2253. this.trigger('done');
  2254. };
  2255. this.endTimeline = function () {
  2256. this.flush();
  2257. this.trigger('endedtimeline');
  2258. };
  2259. this.discontinuity = function () {
  2260. referenceDTS = void 0;
  2261. lastDTS = void 0;
  2262. };
  2263. this.reset = function () {
  2264. this.discontinuity();
  2265. this.trigger('reset');
  2266. };
  2267. };
  2268. TimestampRolloverStream$1.prototype = new stream();
  2269. var timestampRolloverStream = {
  2270. TimestampRolloverStream: TimestampRolloverStream$1,
  2271. handleRollover: handleRollover
  2272. };
  2273. // IE11 doesn't support indexOf for TypedArrays.
  2274. // Once IE11 support is dropped, this function should be removed.
  2275. var typedArrayIndexOf$1 = function typedArrayIndexOf(typedArray, element, fromIndex) {
  2276. if (!typedArray) {
  2277. return -1;
  2278. }
  2279. var currentIndex = fromIndex;
  2280. for (; currentIndex < typedArray.length; currentIndex++) {
  2281. if (typedArray[currentIndex] === element) {
  2282. return currentIndex;
  2283. }
  2284. }
  2285. return -1;
  2286. };
  2287. var typedArray = {
  2288. typedArrayIndexOf: typedArrayIndexOf$1
  2289. };
  2290. var typedArrayIndexOf = typedArray.typedArrayIndexOf,
  2291. // Frames that allow different types of text encoding contain a text
  2292. // encoding description byte [ID3v2.4.0 section 4.]
  2293. textEncodingDescriptionByte = {
  2294. Iso88591: 0x00,
  2295. // ISO-8859-1, terminated with \0.
  2296. Utf16: 0x01,
  2297. // UTF-16 encoded Unicode BOM, terminated with \0\0
  2298. Utf16be: 0x02,
  2299. // UTF-16BE encoded Unicode, without BOM, terminated with \0\0
  2300. Utf8: 0x03 // UTF-8 encoded Unicode, terminated with \0
  2301. },
  2302. // return a percent-encoded representation of the specified byte range
  2303. // @see http://en.wikipedia.org/wiki/Percent-encoding
  2304. percentEncode = function percentEncode(bytes, start, end) {
  2305. var i,
  2306. result = '';
  2307. for (i = start; i < end; i++) {
  2308. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  2309. }
  2310. return result;
  2311. },
  2312. // return the string representation of the specified byte range,
  2313. // interpreted as UTf-8.
  2314. parseUtf8 = function parseUtf8(bytes, start, end) {
  2315. return decodeURIComponent(percentEncode(bytes, start, end));
  2316. },
  2317. // return the string representation of the specified byte range,
  2318. // interpreted as ISO-8859-1.
  2319. parseIso88591 = function parseIso88591(bytes, start, end) {
  2320. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  2321. },
  2322. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  2323. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  2324. },
  2325. frameParsers = {
  2326. 'APIC': function APIC(frame) {
  2327. var i = 1,
  2328. mimeTypeEndIndex,
  2329. descriptionEndIndex,
  2330. LINK_MIME_TYPE = '-->';
  2331. if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
  2332. // ignore frames with unrecognized character encodings
  2333. return;
  2334. } // parsing fields [ID3v2.4.0 section 4.14.]
  2335. mimeTypeEndIndex = typedArrayIndexOf(frame.data, 0, i);
  2336. if (mimeTypeEndIndex < 0) {
  2337. // malformed frame
  2338. return;
  2339. } // parsing Mime type field (terminated with \0)
  2340. frame.mimeType = parseIso88591(frame.data, i, mimeTypeEndIndex);
  2341. i = mimeTypeEndIndex + 1; // parsing 1-byte Picture Type field
  2342. frame.pictureType = frame.data[i];
  2343. i++;
  2344. descriptionEndIndex = typedArrayIndexOf(frame.data, 0, i);
  2345. if (descriptionEndIndex < 0) {
  2346. // malformed frame
  2347. return;
  2348. } // parsing Description field (terminated with \0)
  2349. frame.description = parseUtf8(frame.data, i, descriptionEndIndex);
  2350. i = descriptionEndIndex + 1;
  2351. if (frame.mimeType === LINK_MIME_TYPE) {
  2352. // parsing Picture Data field as URL (always represented as ISO-8859-1 [ID3v2.4.0 section 4.])
  2353. frame.url = parseIso88591(frame.data, i, frame.data.length);
  2354. } else {
  2355. // parsing Picture Data field as binary data
  2356. frame.pictureData = frame.data.subarray(i, frame.data.length);
  2357. }
  2358. },
  2359. 'T*': function T(frame) {
  2360. if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
  2361. // ignore frames with unrecognized character encodings
  2362. return;
  2363. } // parse text field, do not include null terminator in the frame value
  2364. // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
  2365. 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.]
  2366. frame.values = frame.value.split('\0');
  2367. },
  2368. 'TXXX': function TXXX(frame) {
  2369. var descriptionEndIndex;
  2370. if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
  2371. // ignore frames with unrecognized character encodings
  2372. return;
  2373. }
  2374. descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
  2375. if (descriptionEndIndex === -1) {
  2376. return;
  2377. } // parse the text fields
  2378. frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // do not include the null terminator in the tag value
  2379. // frames that allow different types of encoding contain terminated text
  2380. // [ID3v2.4.0 section 4.]
  2381. frame.value = parseUtf8(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0*$/, '');
  2382. frame.data = frame.value;
  2383. },
  2384. 'W*': function W(frame) {
  2385. // parse URL field; URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
  2386. // if the value is followed by a string termination all the following information should be ignored [ID3v2.4.0 section 4.3]
  2387. frame.url = parseIso88591(frame.data, 0, frame.data.length).replace(/\0.*$/, '');
  2388. },
  2389. 'WXXX': function WXXX(frame) {
  2390. var descriptionEndIndex;
  2391. if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
  2392. // ignore frames with unrecognized character encodings
  2393. return;
  2394. }
  2395. descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
  2396. if (descriptionEndIndex === -1) {
  2397. return;
  2398. } // parse the description and URL fields
  2399. frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
  2400. // if the value is followed by a string termination all the following information
  2401. // should be ignored [ID3v2.4.0 section 4.3]
  2402. frame.url = parseIso88591(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0.*$/, '');
  2403. },
  2404. 'PRIV': function PRIV(frame) {
  2405. var i;
  2406. for (i = 0; i < frame.data.length; i++) {
  2407. if (frame.data[i] === 0) {
  2408. // parse the description and URL fields
  2409. frame.owner = parseIso88591(frame.data, 0, i);
  2410. break;
  2411. }
  2412. }
  2413. frame.privateData = frame.data.subarray(i + 1);
  2414. frame.data = frame.privateData;
  2415. }
  2416. };
  2417. var parseId3Frames = function parseId3Frames(data) {
  2418. var frameSize,
  2419. frameHeader,
  2420. frameStart = 10,
  2421. tagSize = 0,
  2422. frames = []; // If we don't have enough data for a header, 10 bytes,
  2423. // or 'ID3' in the first 3 bytes this is not a valid ID3 tag.
  2424. if (data.length < 10 || data[0] !== 'I'.charCodeAt(0) || data[1] !== 'D'.charCodeAt(0) || data[2] !== '3'.charCodeAt(0)) {
  2425. return;
  2426. } // the frame size is transmitted as a 28-bit integer in the
  2427. // last four bytes of the ID3 header.
  2428. // The most significant bit of each byte is dropped and the
  2429. // results concatenated to recover the actual value.
  2430. tagSize = parseSyncSafeInteger(data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  2431. // convenient for our comparisons to include it
  2432. tagSize += 10; // check bit 6 of byte 5 for the extended header flag.
  2433. var hasExtendedHeader = data[5] & 0x40;
  2434. if (hasExtendedHeader) {
  2435. // advance the frame start past the extended header
  2436. frameStart += 4; // header size field
  2437. frameStart += parseSyncSafeInteger(data.subarray(10, 14));
  2438. tagSize -= parseSyncSafeInteger(data.subarray(16, 20)); // clip any padding off the end
  2439. } // parse one or more ID3 frames
  2440. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  2441. do {
  2442. // determine the number of bytes in this frame
  2443. frameSize = parseSyncSafeInteger(data.subarray(frameStart + 4, frameStart + 8));
  2444. if (frameSize < 1) {
  2445. break;
  2446. }
  2447. frameHeader = String.fromCharCode(data[frameStart], data[frameStart + 1], data[frameStart + 2], data[frameStart + 3]);
  2448. var frame = {
  2449. id: frameHeader,
  2450. data: data.subarray(frameStart + 10, frameStart + frameSize + 10)
  2451. };
  2452. frame.key = frame.id; // parse frame values
  2453. if (frameParsers[frame.id]) {
  2454. // use frame specific parser
  2455. frameParsers[frame.id](frame);
  2456. } else if (frame.id[0] === 'T') {
  2457. // use text frame generic parser
  2458. frameParsers['T*'](frame);
  2459. } else if (frame.id[0] === 'W') {
  2460. // use URL link frame generic parser
  2461. frameParsers['W*'](frame);
  2462. }
  2463. frames.push(frame);
  2464. frameStart += 10; // advance past the frame header
  2465. frameStart += frameSize; // advance past the frame body
  2466. } while (frameStart < tagSize);
  2467. return frames;
  2468. };
  2469. var parseId3 = {
  2470. parseId3Frames: parseId3Frames,
  2471. parseSyncSafeInteger: parseSyncSafeInteger,
  2472. frameParsers: frameParsers
  2473. };
  2474. var _MetadataStream;
  2475. _MetadataStream = function MetadataStream(options) {
  2476. var settings = {
  2477. // the bytes of the program-level descriptor field in MP2T
  2478. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  2479. // program element descriptors"
  2480. descriptor: options && options.descriptor
  2481. },
  2482. // the total size in bytes of the ID3 tag being parsed
  2483. tagSize = 0,
  2484. // tag data that is not complete enough to be parsed
  2485. buffer = [],
  2486. // the total number of bytes currently in the buffer
  2487. bufferSize = 0,
  2488. i;
  2489. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  2490. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  2491. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  2492. if (settings.descriptor) {
  2493. for (i = 0; i < settings.descriptor.length; i++) {
  2494. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  2495. }
  2496. }
  2497. this.push = function (chunk) {
  2498. var tag, frameStart, frameSize, frame, i, frameHeader;
  2499. if (chunk.type !== 'timed-metadata') {
  2500. return;
  2501. } // if data_alignment_indicator is set in the PES header,
  2502. // we must have the start of a new ID3 tag. Assume anything
  2503. // remaining in the buffer was malformed and throw it out
  2504. if (chunk.dataAlignmentIndicator) {
  2505. bufferSize = 0;
  2506. buffer.length = 0;
  2507. } // ignore events that don't look like ID3 data
  2508. 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))) {
  2509. this.trigger('log', {
  2510. level: 'warn',
  2511. message: 'Skipping unrecognized metadata packet'
  2512. });
  2513. return;
  2514. } // add this chunk to the data we've collected so far
  2515. buffer.push(chunk);
  2516. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  2517. if (buffer.length === 1) {
  2518. // the frame size is transmitted as a 28-bit integer in the
  2519. // last four bytes of the ID3 header.
  2520. // The most significant bit of each byte is dropped and the
  2521. // results concatenated to recover the actual value.
  2522. tagSize = parseId3.parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  2523. // convenient for our comparisons to include it
  2524. tagSize += 10;
  2525. } // if the entire frame has not arrived, wait for more data
  2526. if (bufferSize < tagSize) {
  2527. return;
  2528. } // collect the entire frame so it can be parsed
  2529. tag = {
  2530. data: new Uint8Array(tagSize),
  2531. frames: [],
  2532. pts: buffer[0].pts,
  2533. dts: buffer[0].dts
  2534. };
  2535. for (i = 0; i < tagSize;) {
  2536. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  2537. i += buffer[0].data.byteLength;
  2538. bufferSize -= buffer[0].data.byteLength;
  2539. buffer.shift();
  2540. } // find the start of the first frame and the end of the tag
  2541. frameStart = 10;
  2542. if (tag.data[5] & 0x40) {
  2543. // advance the frame start past the extended header
  2544. frameStart += 4; // header size field
  2545. frameStart += parseId3.parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  2546. tagSize -= parseId3.parseSyncSafeInteger(tag.data.subarray(16, 20));
  2547. } // parse one or more ID3 frames
  2548. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  2549. do {
  2550. // determine the number of bytes in this frame
  2551. frameSize = parseId3.parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  2552. if (frameSize < 1) {
  2553. this.trigger('log', {
  2554. level: 'warn',
  2555. message: 'Malformed ID3 frame encountered. Skipping remaining metadata parsing.'
  2556. }); // If the frame is malformed, don't parse any further frames but allow previous valid parsed frames
  2557. // to be sent along.
  2558. break;
  2559. }
  2560. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  2561. frame = {
  2562. id: frameHeader,
  2563. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  2564. };
  2565. frame.key = frame.id; // parse frame values
  2566. if (parseId3.frameParsers[frame.id]) {
  2567. // use frame specific parser
  2568. parseId3.frameParsers[frame.id](frame);
  2569. } else if (frame.id[0] === 'T') {
  2570. // use text frame generic parser
  2571. parseId3.frameParsers['T*'](frame);
  2572. } else if (frame.id[0] === 'W') {
  2573. // use URL link frame generic parser
  2574. parseId3.frameParsers['W*'](frame);
  2575. } // handle the special PRIV frame used to indicate the start
  2576. // time for raw AAC data
  2577. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  2578. var d = frame.data,
  2579. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  2580. size *= 4;
  2581. size += d[7] & 0x03;
  2582. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  2583. // on the value of this frame
  2584. // we couldn't have known the appropriate pts and dts before
  2585. // parsing this ID3 tag so set those values now
  2586. if (tag.pts === undefined && tag.dts === undefined) {
  2587. tag.pts = frame.timeStamp;
  2588. tag.dts = frame.timeStamp;
  2589. }
  2590. this.trigger('timestamp', frame);
  2591. }
  2592. tag.frames.push(frame);
  2593. frameStart += 10; // advance past the frame header
  2594. frameStart += frameSize; // advance past the frame body
  2595. } while (frameStart < tagSize);
  2596. this.trigger('data', tag);
  2597. };
  2598. };
  2599. _MetadataStream.prototype = new stream();
  2600. var metadataStream = _MetadataStream;
  2601. var TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
  2602. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  2603. var MP2T_PACKET_LENGTH = 188,
  2604. // bytes
  2605. SYNC_BYTE = 0x47;
  2606. /**
  2607. * Splits an incoming stream of binary data into MPEG-2 Transport
  2608. * Stream packets.
  2609. */
  2610. _TransportPacketStream = function TransportPacketStream() {
  2611. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  2612. bytesInBuffer = 0;
  2613. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  2614. /**
  2615. * Split a stream of data into M2TS packets
  2616. **/
  2617. this.push = function (bytes) {
  2618. var startIndex = 0,
  2619. endIndex = MP2T_PACKET_LENGTH,
  2620. everything; // If there are bytes remaining from the last segment, prepend them to the
  2621. // bytes that were pushed in
  2622. if (bytesInBuffer) {
  2623. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  2624. everything.set(buffer.subarray(0, bytesInBuffer));
  2625. everything.set(bytes, bytesInBuffer);
  2626. bytesInBuffer = 0;
  2627. } else {
  2628. everything = bytes;
  2629. } // While we have enough data for a packet
  2630. while (endIndex < everything.byteLength) {
  2631. // Look for a pair of start and end sync bytes in the data..
  2632. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  2633. // We found a packet so emit it and jump one whole packet forward in
  2634. // the stream
  2635. this.trigger('data', everything.subarray(startIndex, endIndex));
  2636. startIndex += MP2T_PACKET_LENGTH;
  2637. endIndex += MP2T_PACKET_LENGTH;
  2638. continue;
  2639. } // If we get here, we have somehow become de-synchronized and we need to step
  2640. // forward one byte at a time until we find a pair of sync bytes that denote
  2641. // a packet
  2642. startIndex++;
  2643. endIndex++;
  2644. } // If there was some data left over at the end of the segment that couldn't
  2645. // possibly be a whole packet, keep it because it might be the start of a packet
  2646. // that continues in the next segment
  2647. if (startIndex < everything.byteLength) {
  2648. buffer.set(everything.subarray(startIndex), 0);
  2649. bytesInBuffer = everything.byteLength - startIndex;
  2650. }
  2651. };
  2652. /**
  2653. * Passes identified M2TS packets to the TransportParseStream to be parsed
  2654. **/
  2655. this.flush = function () {
  2656. // If the buffer contains a whole packet when we are being flushed, emit it
  2657. // and empty the buffer. Otherwise hold onto the data because it may be
  2658. // important for decoding the next segment
  2659. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  2660. this.trigger('data', buffer);
  2661. bytesInBuffer = 0;
  2662. }
  2663. this.trigger('done');
  2664. };
  2665. this.endTimeline = function () {
  2666. this.flush();
  2667. this.trigger('endedtimeline');
  2668. };
  2669. this.reset = function () {
  2670. bytesInBuffer = 0;
  2671. this.trigger('reset');
  2672. };
  2673. };
  2674. _TransportPacketStream.prototype = new stream();
  2675. /**
  2676. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  2677. * forms of the individual transport stream packets.
  2678. */
  2679. _TransportParseStream = function TransportParseStream() {
  2680. var parsePsi, parsePat, parsePmt, self;
  2681. _TransportParseStream.prototype.init.call(this);
  2682. self = this;
  2683. this.packetsWaitingForPmt = [];
  2684. this.programMapTable = undefined;
  2685. parsePsi = function parsePsi(payload, psi) {
  2686. var offset = 0; // PSI packets may be split into multiple sections and those
  2687. // sections may be split into multiple packets. If a PSI
  2688. // section starts in this packet, the payload_unit_start_indicator
  2689. // will be true and the first byte of the payload will indicate
  2690. // the offset from the current position to the start of the
  2691. // section.
  2692. if (psi.payloadUnitStartIndicator) {
  2693. offset += payload[offset] + 1;
  2694. }
  2695. if (psi.type === 'pat') {
  2696. parsePat(payload.subarray(offset), psi);
  2697. } else {
  2698. parsePmt(payload.subarray(offset), psi);
  2699. }
  2700. };
  2701. parsePat = function parsePat(payload, pat) {
  2702. pat.section_number = payload[7]; // eslint-disable-line camelcase
  2703. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  2704. // skip the PSI header and parse the first PMT entry
  2705. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  2706. pat.pmtPid = self.pmtPid;
  2707. };
  2708. /**
  2709. * Parse out the relevant fields of a Program Map Table (PMT).
  2710. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  2711. * packet. The first byte in this array should be the table_id
  2712. * field.
  2713. * @param pmt {object} the object that should be decorated with
  2714. * fields parsed from the PMT.
  2715. */
  2716. parsePmt = function parsePmt(payload, pmt) {
  2717. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  2718. // take effect. We don't believe this should ever be the case
  2719. // for HLS but we'll ignore "forward" PMT declarations if we see
  2720. // them. Future PMT declarations have the current_next_indicator
  2721. // set to zero.
  2722. if (!(payload[5] & 0x01)) {
  2723. return;
  2724. } // overwrite any existing program map table
  2725. self.programMapTable = {
  2726. video: null,
  2727. audio: null,
  2728. 'timed-metadata': {}
  2729. }; // the mapping table ends at the end of the current section
  2730. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  2731. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  2732. // long the program info descriptors are
  2733. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  2734. offset = 12 + programInfoLength;
  2735. while (offset < tableEnd) {
  2736. var streamType = payload[offset];
  2737. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  2738. // TODO: should this be done for metadata too? for now maintain behavior of
  2739. // multiple metadata streams
  2740. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  2741. self.programMapTable.video = pid;
  2742. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  2743. self.programMapTable.audio = pid;
  2744. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  2745. // map pid to stream type for metadata streams
  2746. self.programMapTable['timed-metadata'][pid] = streamType;
  2747. } // move to the next table entry
  2748. // skip past the elementary stream descriptors, if present
  2749. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  2750. } // record the map on the packet as well
  2751. pmt.programMapTable = self.programMapTable;
  2752. };
  2753. /**
  2754. * Deliver a new MP2T packet to the next stream in the pipeline.
  2755. */
  2756. this.push = function (packet) {
  2757. var result = {},
  2758. offset = 4;
  2759. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  2760. result.pid = packet[1] & 0x1f;
  2761. result.pid <<= 8;
  2762. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  2763. // fifth byte of the TS packet header. The adaptation field is
  2764. // used to add stuffing to PES packets that don't fill a complete
  2765. // TS packet, and to specify some forms of timing and control data
  2766. // that we do not currently use.
  2767. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  2768. offset += packet[offset] + 1;
  2769. } // parse the rest of the packet based on the type
  2770. if (result.pid === 0) {
  2771. result.type = 'pat';
  2772. parsePsi(packet.subarray(offset), result);
  2773. this.trigger('data', result);
  2774. } else if (result.pid === this.pmtPid) {
  2775. result.type = 'pmt';
  2776. parsePsi(packet.subarray(offset), result);
  2777. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  2778. while (this.packetsWaitingForPmt.length) {
  2779. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  2780. }
  2781. } else if (this.programMapTable === undefined) {
  2782. // When we have not seen a PMT yet, defer further processing of
  2783. // PES packets until one has been parsed
  2784. this.packetsWaitingForPmt.push([packet, offset, result]);
  2785. } else {
  2786. this.processPes_(packet, offset, result);
  2787. }
  2788. };
  2789. this.processPes_ = function (packet, offset, result) {
  2790. // set the appropriate stream type
  2791. if (result.pid === this.programMapTable.video) {
  2792. result.streamType = streamTypes.H264_STREAM_TYPE;
  2793. } else if (result.pid === this.programMapTable.audio) {
  2794. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  2795. } else {
  2796. // if not video or audio, it is timed-metadata or unknown
  2797. // if unknown, streamType will be undefined
  2798. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  2799. }
  2800. result.type = 'pes';
  2801. result.data = packet.subarray(offset);
  2802. this.trigger('data', result);
  2803. };
  2804. };
  2805. _TransportParseStream.prototype = new stream();
  2806. _TransportParseStream.STREAM_TYPES = {
  2807. h264: 0x1b,
  2808. adts: 0x0f
  2809. };
  2810. /**
  2811. * Reconsistutes program elementary stream (PES) packets from parsed
  2812. * transport stream packets. That is, if you pipe an
  2813. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  2814. * events will be events which capture the bytes for individual PES
  2815. * packets plus relevant metadata that has been extracted from the
  2816. * container.
  2817. */
  2818. _ElementaryStream = function ElementaryStream() {
  2819. var self = this,
  2820. segmentHadPmt = false,
  2821. // PES packet fragments
  2822. video = {
  2823. data: [],
  2824. size: 0
  2825. },
  2826. audio = {
  2827. data: [],
  2828. size: 0
  2829. },
  2830. timedMetadata = {
  2831. data: [],
  2832. size: 0
  2833. },
  2834. programMapTable,
  2835. parsePes = function parsePes(payload, pes) {
  2836. var ptsDtsFlags;
  2837. var startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array
  2838. pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
  2839. // that are frame data that is continuing from the previous fragment. This
  2840. // is to check that the pes data is the start of a new pes payload
  2841. if (startPrefix !== 1) {
  2842. return;
  2843. } // get the packet length, this will be 0 for video
  2844. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  2845. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  2846. // and a DTS value. Determine what combination of values is
  2847. // available to work with.
  2848. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  2849. // performs all bitwise operations on 32-bit integers but javascript
  2850. // supports a much greater range (52-bits) of integer using standard
  2851. // mathematical operations.
  2852. // We construct a 31-bit value using bitwise operators over the 31
  2853. // most significant bits and then multiply by 4 (equal to a left-shift
  2854. // of 2) before we add the final 2 least significant bits of the
  2855. // timestamp (equal to an OR.)
  2856. if (ptsDtsFlags & 0xC0) {
  2857. // the PTS and DTS are not written out directly. For information
  2858. // on how they are encoded, see
  2859. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  2860. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  2861. pes.pts *= 4; // Left shift by 2
  2862. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  2863. pes.dts = pes.pts;
  2864. if (ptsDtsFlags & 0x40) {
  2865. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  2866. pes.dts *= 4; // Left shift by 2
  2867. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  2868. }
  2869. } // the data section starts immediately after the PES header.
  2870. // pes_header_data_length specifies the number of header bytes
  2871. // that follow the last byte of the field.
  2872. pes.data = payload.subarray(9 + payload[8]);
  2873. },
  2874. /**
  2875. * Pass completely parsed PES packets to the next stream in the pipeline
  2876. **/
  2877. flushStream = function flushStream(stream, type, forceFlush) {
  2878. var packetData = new Uint8Array(stream.size),
  2879. event = {
  2880. type: type
  2881. },
  2882. i = 0,
  2883. offset = 0,
  2884. packetFlushable = false,
  2885. fragment; // do nothing if there is not enough buffered data for a complete
  2886. // PES header
  2887. if (!stream.data.length || stream.size < 9) {
  2888. return;
  2889. }
  2890. event.trackId = stream.data[0].pid; // reassemble the packet
  2891. for (i = 0; i < stream.data.length; i++) {
  2892. fragment = stream.data[i];
  2893. packetData.set(fragment.data, offset);
  2894. offset += fragment.data.byteLength;
  2895. } // parse assembled packet's PES header
  2896. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  2897. // check that there is enough stream data to fill the packet
  2898. packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
  2899. if (forceFlush || packetFlushable) {
  2900. stream.size = 0;
  2901. stream.data.length = 0;
  2902. } // only emit packets that are complete. this is to avoid assembling
  2903. // incomplete PES packets due to poor segmentation
  2904. if (packetFlushable) {
  2905. self.trigger('data', event);
  2906. }
  2907. };
  2908. _ElementaryStream.prototype.init.call(this);
  2909. /**
  2910. * Identifies M2TS packet types and parses PES packets using metadata
  2911. * parsed from the PMT
  2912. **/
  2913. this.push = function (data) {
  2914. ({
  2915. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  2916. // have any meaningful metadata
  2917. },
  2918. pes: function pes() {
  2919. var stream, streamType;
  2920. switch (data.streamType) {
  2921. case streamTypes.H264_STREAM_TYPE:
  2922. stream = video;
  2923. streamType = 'video';
  2924. break;
  2925. case streamTypes.ADTS_STREAM_TYPE:
  2926. stream = audio;
  2927. streamType = 'audio';
  2928. break;
  2929. case streamTypes.METADATA_STREAM_TYPE:
  2930. stream = timedMetadata;
  2931. streamType = 'timed-metadata';
  2932. break;
  2933. default:
  2934. // ignore unknown stream types
  2935. return;
  2936. } // if a new packet is starting, we can flush the completed
  2937. // packet
  2938. if (data.payloadUnitStartIndicator) {
  2939. flushStream(stream, streamType, true);
  2940. } // buffer this fragment until we are sure we've received the
  2941. // complete payload
  2942. stream.data.push(data);
  2943. stream.size += data.data.byteLength;
  2944. },
  2945. pmt: function pmt() {
  2946. var event = {
  2947. type: 'metadata',
  2948. tracks: []
  2949. };
  2950. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  2951. if (programMapTable.video !== null) {
  2952. event.tracks.push({
  2953. timelineStartInfo: {
  2954. baseMediaDecodeTime: 0
  2955. },
  2956. id: +programMapTable.video,
  2957. codec: 'avc',
  2958. type: 'video'
  2959. });
  2960. }
  2961. if (programMapTable.audio !== null) {
  2962. event.tracks.push({
  2963. timelineStartInfo: {
  2964. baseMediaDecodeTime: 0
  2965. },
  2966. id: +programMapTable.audio,
  2967. codec: 'adts',
  2968. type: 'audio'
  2969. });
  2970. }
  2971. segmentHadPmt = true;
  2972. self.trigger('data', event);
  2973. }
  2974. })[data.type]();
  2975. };
  2976. this.reset = function () {
  2977. video.size = 0;
  2978. video.data.length = 0;
  2979. audio.size = 0;
  2980. audio.data.length = 0;
  2981. this.trigger('reset');
  2982. };
  2983. /**
  2984. * Flush any remaining input. Video PES packets may be of variable
  2985. * length. Normally, the start of a new video packet can trigger the
  2986. * finalization of the previous packet. That is not possible if no
  2987. * more video is forthcoming, however. In that case, some other
  2988. * mechanism (like the end of the file) has to be employed. When it is
  2989. * clear that no additional data is forthcoming, calling this method
  2990. * will flush the buffered packets.
  2991. */
  2992. this.flushStreams_ = function () {
  2993. // !!THIS ORDER IS IMPORTANT!!
  2994. // video first then audio
  2995. flushStream(video, 'video');
  2996. flushStream(audio, 'audio');
  2997. flushStream(timedMetadata, 'timed-metadata');
  2998. };
  2999. this.flush = function () {
  3000. // if on flush we haven't had a pmt emitted
  3001. // and we have a pmt to emit. emit the pmt
  3002. // so that we trigger a trackinfo downstream.
  3003. if (!segmentHadPmt && programMapTable) {
  3004. var pmt = {
  3005. type: 'metadata',
  3006. tracks: []
  3007. }; // translate audio and video streams to tracks
  3008. if (programMapTable.video !== null) {
  3009. pmt.tracks.push({
  3010. timelineStartInfo: {
  3011. baseMediaDecodeTime: 0
  3012. },
  3013. id: +programMapTable.video,
  3014. codec: 'avc',
  3015. type: 'video'
  3016. });
  3017. }
  3018. if (programMapTable.audio !== null) {
  3019. pmt.tracks.push({
  3020. timelineStartInfo: {
  3021. baseMediaDecodeTime: 0
  3022. },
  3023. id: +programMapTable.audio,
  3024. codec: 'adts',
  3025. type: 'audio'
  3026. });
  3027. }
  3028. self.trigger('data', pmt);
  3029. }
  3030. segmentHadPmt = false;
  3031. this.flushStreams_();
  3032. this.trigger('done');
  3033. };
  3034. };
  3035. _ElementaryStream.prototype = new stream();
  3036. var m2ts = {
  3037. PAT_PID: 0x0000,
  3038. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  3039. TransportPacketStream: _TransportPacketStream,
  3040. TransportParseStream: _TransportParseStream,
  3041. ElementaryStream: _ElementaryStream,
  3042. TimestampRolloverStream: TimestampRolloverStream,
  3043. CaptionStream: captionStream.CaptionStream,
  3044. Cea608Stream: captionStream.Cea608Stream,
  3045. Cea708Stream: captionStream.Cea708Stream,
  3046. MetadataStream: metadataStream
  3047. };
  3048. for (var type in streamTypes) {
  3049. if (streamTypes.hasOwnProperty(type)) {
  3050. m2ts[type] = streamTypes[type];
  3051. }
  3052. }
  3053. var m2ts_1 = m2ts;
  3054. /**
  3055. * mux.js
  3056. *
  3057. * Copyright (c) Brightcove
  3058. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  3059. */
  3060. var ONE_SECOND_IN_TS$1 = 90000,
  3061. // 90kHz clock
  3062. secondsToVideoTs,
  3063. secondsToAudioTs,
  3064. videoTsToSeconds,
  3065. audioTsToSeconds,
  3066. audioTsToVideoTs,
  3067. videoTsToAudioTs,
  3068. metadataTsToSeconds;
  3069. secondsToVideoTs = function secondsToVideoTs(seconds) {
  3070. return seconds * ONE_SECOND_IN_TS$1;
  3071. };
  3072. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  3073. return seconds * sampleRate;
  3074. };
  3075. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  3076. return timestamp / ONE_SECOND_IN_TS$1;
  3077. };
  3078. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  3079. return timestamp / sampleRate;
  3080. };
  3081. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  3082. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  3083. };
  3084. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  3085. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  3086. };
  3087. /**
  3088. * Adjust ID3 tag or caption timing information by the timeline pts values
  3089. * (if keepOriginalTimestamps is false) and convert to seconds
  3090. */
  3091. metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
  3092. return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
  3093. };
  3094. var clock = {
  3095. ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$1,
  3096. secondsToVideoTs: secondsToVideoTs,
  3097. secondsToAudioTs: secondsToAudioTs,
  3098. videoTsToSeconds: videoTsToSeconds,
  3099. audioTsToSeconds: audioTsToSeconds,
  3100. audioTsToVideoTs: audioTsToVideoTs,
  3101. videoTsToAudioTs: videoTsToAudioTs,
  3102. metadataTsToSeconds: metadataTsToSeconds
  3103. };
  3104. var ONE_SECOND_IN_TS = clock.ONE_SECOND_IN_TS;
  3105. var _AdtsStream;
  3106. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  3107. /*
  3108. * Accepts a ElementaryStream and emits data events with parsed
  3109. * AAC Audio Frames of the individual packets. Input audio in ADTS
  3110. * format is unpacked and re-emitted as AAC frames.
  3111. *
  3112. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  3113. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  3114. */
  3115. _AdtsStream = function AdtsStream(handlePartialSegments) {
  3116. var buffer,
  3117. frameNum = 0;
  3118. _AdtsStream.prototype.init.call(this);
  3119. this.skipWarn_ = function (start, end) {
  3120. this.trigger('log', {
  3121. level: 'warn',
  3122. message: "adts skiping bytes " + start + " to " + end + " in frame " + frameNum + " outside syncword"
  3123. });
  3124. };
  3125. this.push = function (packet) {
  3126. var i = 0,
  3127. frameLength,
  3128. protectionSkipBytes,
  3129. oldBuffer,
  3130. sampleCount,
  3131. adtsFrameDuration;
  3132. if (!handlePartialSegments) {
  3133. frameNum = 0;
  3134. }
  3135. if (packet.type !== 'audio') {
  3136. // ignore non-audio data
  3137. return;
  3138. } // Prepend any data in the buffer to the input data so that we can parse
  3139. // aac frames the cross a PES packet boundary
  3140. if (buffer && buffer.length) {
  3141. oldBuffer = buffer;
  3142. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  3143. buffer.set(oldBuffer);
  3144. buffer.set(packet.data, oldBuffer.byteLength);
  3145. } else {
  3146. buffer = packet.data;
  3147. } // unpack any ADTS frames which have been fully received
  3148. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  3149. var skip; // We use i + 7 here because we want to be able to parse the entire header.
  3150. // If we don't have enough bytes to do that, then we definitely won't have a full frame.
  3151. while (i + 7 < buffer.length) {
  3152. // Look for the start of an ADTS header..
  3153. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  3154. if (typeof skip !== 'number') {
  3155. skip = i;
  3156. } // If a valid header was not found, jump one forward and attempt to
  3157. // find a valid ADTS header starting at the next byte
  3158. i++;
  3159. continue;
  3160. }
  3161. if (typeof skip === 'number') {
  3162. this.skipWarn_(skip, i);
  3163. skip = null;
  3164. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  3165. // end of the ADTS header
  3166. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  3167. // end of the sync sequence
  3168. // NOTE: frame length includes the size of the header
  3169. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  3170. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  3171. 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,
  3172. // then we have to wait for more data
  3173. if (buffer.byteLength - i < frameLength) {
  3174. break;
  3175. } // Otherwise, deliver the complete AAC frame
  3176. this.trigger('data', {
  3177. pts: packet.pts + frameNum * adtsFrameDuration,
  3178. dts: packet.dts + frameNum * adtsFrameDuration,
  3179. sampleCount: sampleCount,
  3180. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  3181. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  3182. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  3183. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  3184. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  3185. samplesize: 16,
  3186. // data is the frame without it's header
  3187. data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength)
  3188. });
  3189. frameNum++;
  3190. i += frameLength;
  3191. }
  3192. if (typeof skip === 'number') {
  3193. this.skipWarn_(skip, i);
  3194. skip = null;
  3195. } // remove processed bytes from the buffer.
  3196. buffer = buffer.subarray(i);
  3197. };
  3198. this.flush = function () {
  3199. frameNum = 0;
  3200. this.trigger('done');
  3201. };
  3202. this.reset = function () {
  3203. buffer = void 0;
  3204. this.trigger('reset');
  3205. };
  3206. this.endTimeline = function () {
  3207. buffer = void 0;
  3208. this.trigger('endedtimeline');
  3209. };
  3210. };
  3211. _AdtsStream.prototype = new stream();
  3212. var adts = _AdtsStream;
  3213. /**
  3214. * mux.js
  3215. *
  3216. * Copyright (c) Brightcove
  3217. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  3218. */
  3219. var ExpGolomb;
  3220. /**
  3221. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  3222. * scheme used by h264.
  3223. */
  3224. ExpGolomb = function ExpGolomb(workingData) {
  3225. var // the number of bytes left to examine in workingData
  3226. workingBytesAvailable = workingData.byteLength,
  3227. // the current word being examined
  3228. workingWord = 0,
  3229. // :uint
  3230. // the number of bits left to examine in the current word
  3231. workingBitsAvailable = 0; // :uint;
  3232. // ():uint
  3233. this.length = function () {
  3234. return 8 * workingBytesAvailable;
  3235. }; // ():uint
  3236. this.bitsAvailable = function () {
  3237. return 8 * workingBytesAvailable + workingBitsAvailable;
  3238. }; // ():void
  3239. this.loadWord = function () {
  3240. var position = workingData.byteLength - workingBytesAvailable,
  3241. workingBytes = new Uint8Array(4),
  3242. availableBytes = Math.min(4, workingBytesAvailable);
  3243. if (availableBytes === 0) {
  3244. throw new Error('no bytes available');
  3245. }
  3246. workingBytes.set(workingData.subarray(position, position + availableBytes));
  3247. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  3248. workingBitsAvailable = availableBytes * 8;
  3249. workingBytesAvailable -= availableBytes;
  3250. }; // (count:int):void
  3251. this.skipBits = function (count) {
  3252. var skipBytes; // :int
  3253. if (workingBitsAvailable > count) {
  3254. workingWord <<= count;
  3255. workingBitsAvailable -= count;
  3256. } else {
  3257. count -= workingBitsAvailable;
  3258. skipBytes = Math.floor(count / 8);
  3259. count -= skipBytes * 8;
  3260. workingBytesAvailable -= skipBytes;
  3261. this.loadWord();
  3262. workingWord <<= count;
  3263. workingBitsAvailable -= count;
  3264. }
  3265. }; // (size:int):uint
  3266. this.readBits = function (size) {
  3267. var bits = Math.min(workingBitsAvailable, size),
  3268. // :uint
  3269. valu = workingWord >>> 32 - bits; // :uint
  3270. // if size > 31, handle error
  3271. workingBitsAvailable -= bits;
  3272. if (workingBitsAvailable > 0) {
  3273. workingWord <<= bits;
  3274. } else if (workingBytesAvailable > 0) {
  3275. this.loadWord();
  3276. }
  3277. bits = size - bits;
  3278. if (bits > 0) {
  3279. return valu << bits | this.readBits(bits);
  3280. }
  3281. return valu;
  3282. }; // ():uint
  3283. this.skipLeadingZeros = function () {
  3284. var leadingZeroCount; // :uint
  3285. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  3286. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  3287. // the first bit of working word is 1
  3288. workingWord <<= leadingZeroCount;
  3289. workingBitsAvailable -= leadingZeroCount;
  3290. return leadingZeroCount;
  3291. }
  3292. } // we exhausted workingWord and still have not found a 1
  3293. this.loadWord();
  3294. return leadingZeroCount + this.skipLeadingZeros();
  3295. }; // ():void
  3296. this.skipUnsignedExpGolomb = function () {
  3297. this.skipBits(1 + this.skipLeadingZeros());
  3298. }; // ():void
  3299. this.skipExpGolomb = function () {
  3300. this.skipBits(1 + this.skipLeadingZeros());
  3301. }; // ():uint
  3302. this.readUnsignedExpGolomb = function () {
  3303. var clz = this.skipLeadingZeros(); // :uint
  3304. return this.readBits(clz + 1) - 1;
  3305. }; // ():int
  3306. this.readExpGolomb = function () {
  3307. var valu = this.readUnsignedExpGolomb(); // :int
  3308. if (0x01 & valu) {
  3309. // the number is odd if the low order bit is set
  3310. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  3311. }
  3312. return -1 * (valu >>> 1); // divide by two then make it negative
  3313. }; // Some convenience functions
  3314. // :Boolean
  3315. this.readBoolean = function () {
  3316. return this.readBits(1) === 1;
  3317. }; // ():int
  3318. this.readUnsignedByte = function () {
  3319. return this.readBits(8);
  3320. };
  3321. this.loadWord();
  3322. };
  3323. var expGolomb = ExpGolomb;
  3324. var _H264Stream, _NalByteStream;
  3325. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  3326. /**
  3327. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  3328. */
  3329. _NalByteStream = function NalByteStream() {
  3330. var syncPoint = 0,
  3331. i,
  3332. buffer;
  3333. _NalByteStream.prototype.init.call(this);
  3334. /*
  3335. * Scans a byte stream and triggers a data event with the NAL units found.
  3336. * @param {Object} data Event received from H264Stream
  3337. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  3338. *
  3339. * @see H264Stream.push
  3340. */
  3341. this.push = function (data) {
  3342. var swapBuffer;
  3343. if (!buffer) {
  3344. buffer = data.data;
  3345. } else {
  3346. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  3347. swapBuffer.set(buffer);
  3348. swapBuffer.set(data.data, buffer.byteLength);
  3349. buffer = swapBuffer;
  3350. }
  3351. var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
  3352. // scan for NAL unit boundaries
  3353. // a match looks like this:
  3354. // 0 0 1 .. NAL .. 0 0 1
  3355. // ^ sync point ^ i
  3356. // or this:
  3357. // 0 0 1 .. NAL .. 0 0 0
  3358. // ^ sync point ^ i
  3359. // advance the sync point to a NAL start, if necessary
  3360. for (; syncPoint < len - 3; syncPoint++) {
  3361. if (buffer[syncPoint + 2] === 1) {
  3362. // the sync point is properly aligned
  3363. i = syncPoint + 5;
  3364. break;
  3365. }
  3366. }
  3367. while (i < len) {
  3368. // look at the current byte to determine if we've hit the end of
  3369. // a NAL unit boundary
  3370. switch (buffer[i]) {
  3371. case 0:
  3372. // skip past non-sync sequences
  3373. if (buffer[i - 1] !== 0) {
  3374. i += 2;
  3375. break;
  3376. } else if (buffer[i - 2] !== 0) {
  3377. i++;
  3378. break;
  3379. } // deliver the NAL unit if it isn't empty
  3380. if (syncPoint + 3 !== i - 2) {
  3381. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  3382. } // drop trailing zeroes
  3383. do {
  3384. i++;
  3385. } while (buffer[i] !== 1 && i < len);
  3386. syncPoint = i - 2;
  3387. i += 3;
  3388. break;
  3389. case 1:
  3390. // skip past non-sync sequences
  3391. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  3392. i += 3;
  3393. break;
  3394. } // deliver the NAL unit
  3395. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  3396. syncPoint = i - 2;
  3397. i += 3;
  3398. break;
  3399. default:
  3400. // the current byte isn't a one or zero, so it cannot be part
  3401. // of a sync sequence
  3402. i += 3;
  3403. break;
  3404. }
  3405. } // filter out the NAL units that were delivered
  3406. buffer = buffer.subarray(syncPoint);
  3407. i -= syncPoint;
  3408. syncPoint = 0;
  3409. };
  3410. this.reset = function () {
  3411. buffer = null;
  3412. syncPoint = 0;
  3413. this.trigger('reset');
  3414. };
  3415. this.flush = function () {
  3416. // deliver the last buffered NAL unit
  3417. if (buffer && buffer.byteLength > 3) {
  3418. this.trigger('data', buffer.subarray(syncPoint + 3));
  3419. } // reset the stream state
  3420. buffer = null;
  3421. syncPoint = 0;
  3422. this.trigger('done');
  3423. };
  3424. this.endTimeline = function () {
  3425. this.flush();
  3426. this.trigger('endedtimeline');
  3427. };
  3428. };
  3429. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  3430. // see Recommendation ITU-T H.264 (4/2013),
  3431. // 7.3.2.1.1 Sequence parameter set data syntax
  3432. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  3433. 100: true,
  3434. 110: true,
  3435. 122: true,
  3436. 244: true,
  3437. 44: true,
  3438. 83: true,
  3439. 86: true,
  3440. 118: true,
  3441. 128: true,
  3442. // TODO: the three profiles below don't
  3443. // appear to have sps data in the specificiation anymore?
  3444. 138: true,
  3445. 139: true,
  3446. 134: true
  3447. };
  3448. /**
  3449. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  3450. * events.
  3451. */
  3452. _H264Stream = function H264Stream() {
  3453. var nalByteStream = new _NalByteStream(),
  3454. self,
  3455. trackId,
  3456. currentPts,
  3457. currentDts,
  3458. discardEmulationPreventionBytes,
  3459. readSequenceParameterSet,
  3460. skipScalingList;
  3461. _H264Stream.prototype.init.call(this);
  3462. self = this;
  3463. /*
  3464. * Pushes a packet from a stream onto the NalByteStream
  3465. *
  3466. * @param {Object} packet - A packet received from a stream
  3467. * @param {Uint8Array} packet.data - The raw bytes of the packet
  3468. * @param {Number} packet.dts - Decode timestamp of the packet
  3469. * @param {Number} packet.pts - Presentation timestamp of the packet
  3470. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  3471. * @param {('video'|'audio')} packet.type - The type of packet
  3472. *
  3473. */
  3474. this.push = function (packet) {
  3475. if (packet.type !== 'video') {
  3476. return;
  3477. }
  3478. trackId = packet.trackId;
  3479. currentPts = packet.pts;
  3480. currentDts = packet.dts;
  3481. nalByteStream.push(packet);
  3482. };
  3483. /*
  3484. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  3485. * for the NALUs to the next stream component.
  3486. * Also, preprocess caption and sequence parameter NALUs.
  3487. *
  3488. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  3489. * @see NalByteStream.push
  3490. */
  3491. nalByteStream.on('data', function (data) {
  3492. var event = {
  3493. trackId: trackId,
  3494. pts: currentPts,
  3495. dts: currentDts,
  3496. data: data,
  3497. nalUnitTypeCode: data[0] & 0x1f
  3498. };
  3499. switch (event.nalUnitTypeCode) {
  3500. case 0x05:
  3501. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  3502. break;
  3503. case 0x06:
  3504. event.nalUnitType = 'sei_rbsp';
  3505. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  3506. break;
  3507. case 0x07:
  3508. event.nalUnitType = 'seq_parameter_set_rbsp';
  3509. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  3510. event.config = readSequenceParameterSet(event.escapedRBSP);
  3511. break;
  3512. case 0x08:
  3513. event.nalUnitType = 'pic_parameter_set_rbsp';
  3514. break;
  3515. case 0x09:
  3516. event.nalUnitType = 'access_unit_delimiter_rbsp';
  3517. break;
  3518. } // This triggers data on the H264Stream
  3519. self.trigger('data', event);
  3520. });
  3521. nalByteStream.on('done', function () {
  3522. self.trigger('done');
  3523. });
  3524. nalByteStream.on('partialdone', function () {
  3525. self.trigger('partialdone');
  3526. });
  3527. nalByteStream.on('reset', function () {
  3528. self.trigger('reset');
  3529. });
  3530. nalByteStream.on('endedtimeline', function () {
  3531. self.trigger('endedtimeline');
  3532. });
  3533. this.flush = function () {
  3534. nalByteStream.flush();
  3535. };
  3536. this.partialFlush = function () {
  3537. nalByteStream.partialFlush();
  3538. };
  3539. this.reset = function () {
  3540. nalByteStream.reset();
  3541. };
  3542. this.endTimeline = function () {
  3543. nalByteStream.endTimeline();
  3544. };
  3545. /**
  3546. * Advance the ExpGolomb decoder past a scaling list. The scaling
  3547. * list is optionally transmitted as part of a sequence parameter
  3548. * set and is not relevant to transmuxing.
  3549. * @param count {number} the number of entries in this scaling list
  3550. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  3551. * start of a scaling list
  3552. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  3553. */
  3554. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  3555. var lastScale = 8,
  3556. nextScale = 8,
  3557. j,
  3558. deltaScale;
  3559. for (j = 0; j < count; j++) {
  3560. if (nextScale !== 0) {
  3561. deltaScale = expGolombDecoder.readExpGolomb();
  3562. nextScale = (lastScale + deltaScale + 256) % 256;
  3563. }
  3564. lastScale = nextScale === 0 ? lastScale : nextScale;
  3565. }
  3566. };
  3567. /**
  3568. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  3569. * Sequence Payload"
  3570. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  3571. * unit
  3572. * @return {Uint8Array} the RBSP without any Emulation
  3573. * Prevention Bytes
  3574. */
  3575. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  3576. var length = data.byteLength,
  3577. emulationPreventionBytesPositions = [],
  3578. i = 1,
  3579. newLength,
  3580. newData; // Find all `Emulation Prevention Bytes`
  3581. while (i < length - 2) {
  3582. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  3583. emulationPreventionBytesPositions.push(i + 2);
  3584. i += 2;
  3585. } else {
  3586. i++;
  3587. }
  3588. } // If no Emulation Prevention Bytes were found just return the original
  3589. // array
  3590. if (emulationPreventionBytesPositions.length === 0) {
  3591. return data;
  3592. } // Create a new array to hold the NAL unit data
  3593. newLength = length - emulationPreventionBytesPositions.length;
  3594. newData = new Uint8Array(newLength);
  3595. var sourceIndex = 0;
  3596. for (i = 0; i < newLength; sourceIndex++, i++) {
  3597. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  3598. // Skip this byte
  3599. sourceIndex++; // Remove this position index
  3600. emulationPreventionBytesPositions.shift();
  3601. }
  3602. newData[i] = data[sourceIndex];
  3603. }
  3604. return newData;
  3605. };
  3606. /**
  3607. * Read a sequence parameter set and return some interesting video
  3608. * properties. A sequence parameter set is the H264 metadata that
  3609. * describes the properties of upcoming video frames.
  3610. * @param data {Uint8Array} the bytes of a sequence parameter set
  3611. * @return {object} an object with configuration parsed from the
  3612. * sequence parameter set, including the dimensions of the
  3613. * associated video frames.
  3614. */
  3615. readSequenceParameterSet = function readSequenceParameterSet(data) {
  3616. var frameCropLeftOffset = 0,
  3617. frameCropRightOffset = 0,
  3618. frameCropTopOffset = 0,
  3619. frameCropBottomOffset = 0,
  3620. expGolombDecoder,
  3621. profileIdc,
  3622. levelIdc,
  3623. profileCompatibility,
  3624. chromaFormatIdc,
  3625. picOrderCntType,
  3626. numRefFramesInPicOrderCntCycle,
  3627. picWidthInMbsMinus1,
  3628. picHeightInMapUnitsMinus1,
  3629. frameMbsOnlyFlag,
  3630. scalingListCount,
  3631. sarRatio = [1, 1],
  3632. aspectRatioIdc,
  3633. i;
  3634. expGolombDecoder = new expGolomb(data);
  3635. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  3636. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  3637. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  3638. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  3639. // some profiles have more optional data we don't need
  3640. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  3641. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  3642. if (chromaFormatIdc === 3) {
  3643. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  3644. }
  3645. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  3646. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  3647. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  3648. if (expGolombDecoder.readBoolean()) {
  3649. // seq_scaling_matrix_present_flag
  3650. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  3651. for (i = 0; i < scalingListCount; i++) {
  3652. if (expGolombDecoder.readBoolean()) {
  3653. // seq_scaling_list_present_flag[ i ]
  3654. if (i < 6) {
  3655. skipScalingList(16, expGolombDecoder);
  3656. } else {
  3657. skipScalingList(64, expGolombDecoder);
  3658. }
  3659. }
  3660. }
  3661. }
  3662. }
  3663. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  3664. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  3665. if (picOrderCntType === 0) {
  3666. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  3667. } else if (picOrderCntType === 1) {
  3668. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  3669. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  3670. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  3671. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  3672. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  3673. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  3674. }
  3675. }
  3676. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  3677. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  3678. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  3679. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  3680. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  3681. if (frameMbsOnlyFlag === 0) {
  3682. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  3683. }
  3684. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  3685. if (expGolombDecoder.readBoolean()) {
  3686. // frame_cropping_flag
  3687. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  3688. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  3689. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  3690. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  3691. }
  3692. if (expGolombDecoder.readBoolean()) {
  3693. // vui_parameters_present_flag
  3694. if (expGolombDecoder.readBoolean()) {
  3695. // aspect_ratio_info_present_flag
  3696. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  3697. switch (aspectRatioIdc) {
  3698. case 1:
  3699. sarRatio = [1, 1];
  3700. break;
  3701. case 2:
  3702. sarRatio = [12, 11];
  3703. break;
  3704. case 3:
  3705. sarRatio = [10, 11];
  3706. break;
  3707. case 4:
  3708. sarRatio = [16, 11];
  3709. break;
  3710. case 5:
  3711. sarRatio = [40, 33];
  3712. break;
  3713. case 6:
  3714. sarRatio = [24, 11];
  3715. break;
  3716. case 7:
  3717. sarRatio = [20, 11];
  3718. break;
  3719. case 8:
  3720. sarRatio = [32, 11];
  3721. break;
  3722. case 9:
  3723. sarRatio = [80, 33];
  3724. break;
  3725. case 10:
  3726. sarRatio = [18, 11];
  3727. break;
  3728. case 11:
  3729. sarRatio = [15, 11];
  3730. break;
  3731. case 12:
  3732. sarRatio = [64, 33];
  3733. break;
  3734. case 13:
  3735. sarRatio = [160, 99];
  3736. break;
  3737. case 14:
  3738. sarRatio = [4, 3];
  3739. break;
  3740. case 15:
  3741. sarRatio = [3, 2];
  3742. break;
  3743. case 16:
  3744. sarRatio = [2, 1];
  3745. break;
  3746. case 255:
  3747. {
  3748. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  3749. break;
  3750. }
  3751. }
  3752. if (sarRatio) {
  3753. sarRatio[0] / sarRatio[1];
  3754. }
  3755. }
  3756. }
  3757. return {
  3758. profileIdc: profileIdc,
  3759. levelIdc: levelIdc,
  3760. profileCompatibility: profileCompatibility,
  3761. width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
  3762. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
  3763. // sar is sample aspect ratio
  3764. sarRatio: sarRatio
  3765. };
  3766. };
  3767. };
  3768. _H264Stream.prototype = new stream();
  3769. var h264 = {
  3770. H264Stream: _H264Stream,
  3771. NalByteStream: _NalByteStream
  3772. };
  3773. /**
  3774. * The final stage of the transmuxer that emits the flv tags
  3775. * for audio, video, and metadata. Also tranlates in time and
  3776. * outputs caption data and id3 cues.
  3777. */
  3778. var CoalesceStream = function CoalesceStream(options) {
  3779. // Number of Tracks per output segment
  3780. // If greater than 1, we combine multiple
  3781. // tracks into a single segment
  3782. this.numberOfTracks = 0;
  3783. this.metadataStream = options.metadataStream;
  3784. this.videoTags = [];
  3785. this.audioTags = [];
  3786. this.videoTrack = null;
  3787. this.audioTrack = null;
  3788. this.pendingCaptions = [];
  3789. this.pendingMetadata = [];
  3790. this.pendingTracks = 0;
  3791. this.processedTracks = 0;
  3792. CoalesceStream.prototype.init.call(this); // Take output from multiple
  3793. this.push = function (output) {
  3794. // buffer incoming captions until the associated video segment
  3795. // finishes
  3796. if (output.content || output.text) {
  3797. return this.pendingCaptions.push(output);
  3798. } // buffer incoming id3 tags until the final flush
  3799. if (output.frames) {
  3800. return this.pendingMetadata.push(output);
  3801. }
  3802. if (output.track.type === 'video') {
  3803. this.videoTrack = output.track;
  3804. this.videoTags = output.tags;
  3805. this.pendingTracks++;
  3806. }
  3807. if (output.track.type === 'audio') {
  3808. this.audioTrack = output.track;
  3809. this.audioTags = output.tags;
  3810. this.pendingTracks++;
  3811. }
  3812. };
  3813. };
  3814. CoalesceStream.prototype = new stream();
  3815. CoalesceStream.prototype.flush = function (flushSource) {
  3816. var id3,
  3817. caption,
  3818. i,
  3819. timelineStartPts,
  3820. event = {
  3821. tags: {},
  3822. captions: [],
  3823. captionStreams: {},
  3824. metadata: []
  3825. };
  3826. if (this.pendingTracks < this.numberOfTracks) {
  3827. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  3828. // Return because we haven't received a flush from a data-generating
  3829. // portion of the segment (meaning that we have only recieved meta-data
  3830. // or captions.)
  3831. return;
  3832. } else if (this.pendingTracks === 0) {
  3833. // In the case where we receive a flush without any data having been
  3834. // received we consider it an emitted track for the purposes of coalescing
  3835. // `done` events.
  3836. // We do this for the case where there is an audio and video track in the
  3837. // segment but no audio data. (seen in several playlists with alternate
  3838. // audio tracks and no audio present in the main TS segments.)
  3839. this.processedTracks++;
  3840. if (this.processedTracks < this.numberOfTracks) {
  3841. return;
  3842. }
  3843. }
  3844. }
  3845. this.processedTracks += this.pendingTracks;
  3846. this.pendingTracks = 0;
  3847. if (this.processedTracks < this.numberOfTracks) {
  3848. return;
  3849. }
  3850. if (this.videoTrack) {
  3851. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  3852. } else if (this.audioTrack) {
  3853. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  3854. }
  3855. event.tags.videoTags = this.videoTags;
  3856. event.tags.audioTags = this.audioTags; // Translate caption PTS times into second offsets into the
  3857. // video timeline for the segment, and add track info
  3858. for (i = 0; i < this.pendingCaptions.length; i++) {
  3859. caption = this.pendingCaptions[i];
  3860. caption.startTime = caption.startPts - timelineStartPts;
  3861. caption.startTime /= 90e3;
  3862. caption.endTime = caption.endPts - timelineStartPts;
  3863. caption.endTime /= 90e3;
  3864. event.captionStreams[caption.stream] = true;
  3865. event.captions.push(caption);
  3866. } // Translate ID3 frame PTS times into second offsets into the
  3867. // video timeline for the segment
  3868. for (i = 0; i < this.pendingMetadata.length; i++) {
  3869. id3 = this.pendingMetadata[i];
  3870. id3.cueTime = id3.pts - timelineStartPts;
  3871. id3.cueTime /= 90e3;
  3872. event.metadata.push(id3);
  3873. } // We add this to every single emitted segment even though we only need
  3874. // it for the first
  3875. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  3876. this.videoTrack = null;
  3877. this.audioTrack = null;
  3878. this.videoTags = [];
  3879. this.audioTags = [];
  3880. this.pendingCaptions.length = 0;
  3881. this.pendingMetadata.length = 0;
  3882. this.pendingTracks = 0;
  3883. this.processedTracks = 0; // Emit the final segment
  3884. this.trigger('data', event);
  3885. this.trigger('done');
  3886. };
  3887. var coalesceStream = CoalesceStream;
  3888. /**
  3889. * mux.js
  3890. *
  3891. * Copyright (c) Brightcove
  3892. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  3893. */
  3894. var TagList = function TagList() {
  3895. var self = this;
  3896. this.list = [];
  3897. this.push = function (tag) {
  3898. this.list.push({
  3899. bytes: tag.bytes,
  3900. dts: tag.dts,
  3901. pts: tag.pts,
  3902. keyFrame: tag.keyFrame,
  3903. metaDataTag: tag.metaDataTag
  3904. });
  3905. };
  3906. Object.defineProperty(this, 'length', {
  3907. get: function get() {
  3908. return self.list.length;
  3909. }
  3910. });
  3911. };
  3912. var tagList = TagList;
  3913. var H264Stream = h264.H264Stream;
  3914. var _Transmuxer, _VideoSegmentStream, _AudioSegmentStream, collectTimelineInfo, metaDataTag, extraDataTag;
  3915. /**
  3916. * Store information about the start and end of the tracka and the
  3917. * duration for each frame/sample we process in order to calculate
  3918. * the baseMediaDecodeTime
  3919. */
  3920. collectTimelineInfo = function collectTimelineInfo(track, data) {
  3921. if (typeof data.pts === 'number') {
  3922. if (track.timelineStartInfo.pts === undefined) {
  3923. track.timelineStartInfo.pts = data.pts;
  3924. } else {
  3925. track.timelineStartInfo.pts = Math.min(track.timelineStartInfo.pts, data.pts);
  3926. }
  3927. }
  3928. if (typeof data.dts === 'number') {
  3929. if (track.timelineStartInfo.dts === undefined) {
  3930. track.timelineStartInfo.dts = data.dts;
  3931. } else {
  3932. track.timelineStartInfo.dts = Math.min(track.timelineStartInfo.dts, data.dts);
  3933. }
  3934. }
  3935. };
  3936. metaDataTag = function metaDataTag(track, pts) {
  3937. var tag = new flvTag(flvTag.METADATA_TAG); // :FlvTag
  3938. tag.dts = pts;
  3939. tag.pts = pts;
  3940. tag.writeMetaDataDouble('videocodecid', 7);
  3941. tag.writeMetaDataDouble('width', track.width);
  3942. tag.writeMetaDataDouble('height', track.height);
  3943. return tag;
  3944. };
  3945. extraDataTag = function extraDataTag(track, pts) {
  3946. var i,
  3947. tag = new flvTag(flvTag.VIDEO_TAG, true);
  3948. tag.dts = pts;
  3949. tag.pts = pts;
  3950. tag.writeByte(0x01); // version
  3951. tag.writeByte(track.profileIdc); // profile
  3952. tag.writeByte(track.profileCompatibility); // compatibility
  3953. tag.writeByte(track.levelIdc); // level
  3954. tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
  3955. tag.writeByte(0xE0 | 0x01); // reserved (3 bits), num of SPS (5 bits)
  3956. tag.writeShort(track.sps[0].length); // data of SPS
  3957. tag.writeBytes(track.sps[0]); // SPS
  3958. tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?)
  3959. for (i = 0; i < track.pps.length; ++i) {
  3960. tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS
  3961. tag.writeBytes(track.pps[i]); // data of PPS
  3962. }
  3963. return tag;
  3964. };
  3965. /**
  3966. * Constructs a single-track, media segment from AAC data
  3967. * events. The output of this stream can be fed to flash.
  3968. */
  3969. _AudioSegmentStream = function AudioSegmentStream(track) {
  3970. var adtsFrames = [],
  3971. videoKeyFrames = [],
  3972. oldExtraData;
  3973. _AudioSegmentStream.prototype.init.call(this);
  3974. this.push = function (data) {
  3975. collectTimelineInfo(track, data);
  3976. if (track) {
  3977. track.audioobjecttype = data.audioobjecttype;
  3978. track.channelcount = data.channelcount;
  3979. track.samplerate = data.samplerate;
  3980. track.samplingfrequencyindex = data.samplingfrequencyindex;
  3981. track.samplesize = data.samplesize;
  3982. track.extraData = track.audioobjecttype << 11 | track.samplingfrequencyindex << 7 | track.channelcount << 3;
  3983. }
  3984. data.pts = Math.round(data.pts / 90);
  3985. data.dts = Math.round(data.dts / 90); // buffer audio data until end() is called
  3986. adtsFrames.push(data);
  3987. };
  3988. this.flush = function () {
  3989. var currentFrame,
  3990. adtsFrame,
  3991. lastMetaPts,
  3992. tags = new tagList(); // return early if no audio data has been observed
  3993. if (adtsFrames.length === 0) {
  3994. this.trigger('done', 'AudioSegmentStream');
  3995. return;
  3996. }
  3997. lastMetaPts = -Infinity;
  3998. while (adtsFrames.length) {
  3999. currentFrame = adtsFrames.shift(); // write out a metadata frame at every video key frame
  4000. if (videoKeyFrames.length && currentFrame.pts >= videoKeyFrames[0]) {
  4001. lastMetaPts = videoKeyFrames.shift();
  4002. this.writeMetaDataTags(tags, lastMetaPts);
  4003. } // also write out metadata tags every 1 second so that the decoder
  4004. // is re-initialized quickly after seeking into a different
  4005. // audio configuration.
  4006. if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) {
  4007. this.writeMetaDataTags(tags, currentFrame.pts);
  4008. oldExtraData = track.extraData;
  4009. lastMetaPts = currentFrame.pts;
  4010. }
  4011. adtsFrame = new flvTag(flvTag.AUDIO_TAG);
  4012. adtsFrame.pts = currentFrame.pts;
  4013. adtsFrame.dts = currentFrame.dts;
  4014. adtsFrame.writeBytes(currentFrame.data);
  4015. tags.push(adtsFrame.finalize());
  4016. }
  4017. videoKeyFrames.length = 0;
  4018. oldExtraData = null;
  4019. this.trigger('data', {
  4020. track: track,
  4021. tags: tags.list
  4022. });
  4023. this.trigger('done', 'AudioSegmentStream');
  4024. };
  4025. this.writeMetaDataTags = function (tags, pts) {
  4026. var adtsFrame;
  4027. adtsFrame = new flvTag(flvTag.METADATA_TAG); // For audio, DTS is always the same as PTS. We want to set the DTS
  4028. // however so we can compare with video DTS to determine approximate
  4029. // packet order
  4030. adtsFrame.pts = pts;
  4031. adtsFrame.dts = pts; // AAC is always 10
  4032. adtsFrame.writeMetaDataDouble('audiocodecid', 10);
  4033. adtsFrame.writeMetaDataBoolean('stereo', track.channelcount === 2);
  4034. adtsFrame.writeMetaDataDouble('audiosamplerate', track.samplerate); // Is AAC always 16 bit?
  4035. adtsFrame.writeMetaDataDouble('audiosamplesize', 16);
  4036. tags.push(adtsFrame.finalize());
  4037. adtsFrame = new flvTag(flvTag.AUDIO_TAG, true); // For audio, DTS is always the same as PTS. We want to set the DTS
  4038. // however so we can compare with video DTS to determine approximate
  4039. // packet order
  4040. adtsFrame.pts = pts;
  4041. adtsFrame.dts = pts;
  4042. adtsFrame.view.setUint16(adtsFrame.position, track.extraData);
  4043. adtsFrame.position += 2;
  4044. adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position);
  4045. tags.push(adtsFrame.finalize());
  4046. };
  4047. this.onVideoKeyFrame = function (pts) {
  4048. videoKeyFrames.push(pts);
  4049. };
  4050. };
  4051. _AudioSegmentStream.prototype = new stream();
  4052. /**
  4053. * Store FlvTags for the h264 stream
  4054. * @param track {object} track metadata configuration
  4055. */
  4056. _VideoSegmentStream = function VideoSegmentStream(track) {
  4057. var nalUnits = [],
  4058. config,
  4059. h264Frame;
  4060. _VideoSegmentStream.prototype.init.call(this);
  4061. this.finishFrame = function (tags, frame) {
  4062. if (!frame) {
  4063. return;
  4064. } // Check if keyframe and the length of tags.
  4065. // This makes sure we write metadata on the first frame of a segment.
  4066. if (config && track && track.newMetadata && (frame.keyFrame || tags.length === 0)) {
  4067. // Push extra data on every IDR frame in case we did a stream change + seek
  4068. var metaTag = metaDataTag(config, frame.dts).finalize();
  4069. var extraTag = extraDataTag(track, frame.dts).finalize();
  4070. metaTag.metaDataTag = extraTag.metaDataTag = true;
  4071. tags.push(metaTag);
  4072. tags.push(extraTag);
  4073. track.newMetadata = false;
  4074. this.trigger('keyframe', frame.dts);
  4075. }
  4076. frame.endNalUnit();
  4077. tags.push(frame.finalize());
  4078. h264Frame = null;
  4079. };
  4080. this.push = function (data) {
  4081. collectTimelineInfo(track, data);
  4082. data.pts = Math.round(data.pts / 90);
  4083. data.dts = Math.round(data.dts / 90); // buffer video until flush() is called
  4084. nalUnits.push(data);
  4085. };
  4086. this.flush = function () {
  4087. var currentNal,
  4088. tags = new tagList(); // Throw away nalUnits at the start of the byte stream until we find
  4089. // the first AUD
  4090. while (nalUnits.length) {
  4091. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  4092. break;
  4093. }
  4094. nalUnits.shift();
  4095. } // return early if no video data has been observed
  4096. if (nalUnits.length === 0) {
  4097. this.trigger('done', 'VideoSegmentStream');
  4098. return;
  4099. }
  4100. while (nalUnits.length) {
  4101. currentNal = nalUnits.shift(); // record the track config
  4102. if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') {
  4103. track.newMetadata = true;
  4104. config = currentNal.config;
  4105. track.width = config.width;
  4106. track.height = config.height;
  4107. track.sps = [currentNal.data];
  4108. track.profileIdc = config.profileIdc;
  4109. track.levelIdc = config.levelIdc;
  4110. track.profileCompatibility = config.profileCompatibility;
  4111. h264Frame.endNalUnit();
  4112. } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') {
  4113. track.newMetadata = true;
  4114. track.pps = [currentNal.data];
  4115. h264Frame.endNalUnit();
  4116. } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  4117. if (h264Frame) {
  4118. this.finishFrame(tags, h264Frame);
  4119. }
  4120. h264Frame = new flvTag(flvTag.VIDEO_TAG);
  4121. h264Frame.pts = currentNal.pts;
  4122. h264Frame.dts = currentNal.dts;
  4123. } else {
  4124. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  4125. // the current sample is a key frame
  4126. h264Frame.keyFrame = true;
  4127. }
  4128. h264Frame.endNalUnit();
  4129. }
  4130. h264Frame.startNalUnit();
  4131. h264Frame.writeBytes(currentNal.data);
  4132. }
  4133. if (h264Frame) {
  4134. this.finishFrame(tags, h264Frame);
  4135. }
  4136. this.trigger('data', {
  4137. track: track,
  4138. tags: tags.list
  4139. }); // Continue with the flush process now
  4140. this.trigger('done', 'VideoSegmentStream');
  4141. };
  4142. };
  4143. _VideoSegmentStream.prototype = new stream();
  4144. /**
  4145. * An object that incrementally transmuxes MPEG2 Trasport Stream
  4146. * chunks into an FLV.
  4147. */
  4148. _Transmuxer = function Transmuxer(options) {
  4149. var self = this,
  4150. packetStream,
  4151. parseStream,
  4152. elementaryStream,
  4153. videoTimestampRolloverStream,
  4154. audioTimestampRolloverStream,
  4155. timedMetadataTimestampRolloverStream,
  4156. adtsStream,
  4157. h264Stream,
  4158. videoSegmentStream,
  4159. audioSegmentStream,
  4160. captionStream,
  4161. coalesceStream$1;
  4162. _Transmuxer.prototype.init.call(this);
  4163. options = options || {}; // expose the metadata stream
  4164. this.metadataStream = new m2ts_1.MetadataStream();
  4165. options.metadataStream = this.metadataStream; // set up the parsing pipeline
  4166. packetStream = new m2ts_1.TransportPacketStream();
  4167. parseStream = new m2ts_1.TransportParseStream();
  4168. elementaryStream = new m2ts_1.ElementaryStream();
  4169. videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  4170. audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  4171. timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  4172. adtsStream = new adts();
  4173. h264Stream = new H264Stream();
  4174. coalesceStream$1 = new coalesceStream(options); // disassemble MPEG2-TS packets into elementary streams
  4175. packetStream.pipe(parseStream).pipe(elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  4176. // demux the streams
  4177. elementaryStream.pipe(videoTimestampRolloverStream).pipe(h264Stream);
  4178. elementaryStream.pipe(audioTimestampRolloverStream).pipe(adtsStream);
  4179. elementaryStream.pipe(timedMetadataTimestampRolloverStream).pipe(this.metadataStream).pipe(coalesceStream$1); // if CEA-708 parsing is available, hook up a caption stream
  4180. captionStream = new m2ts_1.CaptionStream(options);
  4181. h264Stream.pipe(captionStream).pipe(coalesceStream$1); // hook up the segment streams once track metadata is delivered
  4182. elementaryStream.on('data', function (data) {
  4183. var i, videoTrack, audioTrack;
  4184. if (data.type === 'metadata') {
  4185. i = data.tracks.length; // scan the tracks listed in the metadata
  4186. while (i--) {
  4187. if (data.tracks[i].type === 'video') {
  4188. videoTrack = data.tracks[i];
  4189. } else if (data.tracks[i].type === 'audio') {
  4190. audioTrack = data.tracks[i];
  4191. }
  4192. } // hook up the video segment stream to the first track with h264 data
  4193. if (videoTrack && !videoSegmentStream) {
  4194. coalesceStream$1.numberOfTracks++;
  4195. videoSegmentStream = new _VideoSegmentStream(videoTrack); // Set up the final part of the video pipeline
  4196. h264Stream.pipe(videoSegmentStream).pipe(coalesceStream$1);
  4197. }
  4198. if (audioTrack && !audioSegmentStream) {
  4199. // hook up the audio segment stream to the first track with aac data
  4200. coalesceStream$1.numberOfTracks++;
  4201. audioSegmentStream = new _AudioSegmentStream(audioTrack); // Set up the final part of the audio pipeline
  4202. adtsStream.pipe(audioSegmentStream).pipe(coalesceStream$1);
  4203. if (videoSegmentStream) {
  4204. videoSegmentStream.on('keyframe', audioSegmentStream.onVideoKeyFrame);
  4205. }
  4206. }
  4207. }
  4208. }); // feed incoming data to the front of the parsing pipeline
  4209. this.push = function (data) {
  4210. packetStream.push(data);
  4211. }; // flush any buffered data
  4212. this.flush = function () {
  4213. // Start at the top of the pipeline and flush all pending work
  4214. packetStream.flush();
  4215. }; // Caption data has to be reset when seeking outside buffered range
  4216. this.resetCaptions = function () {
  4217. captionStream.reset();
  4218. }; // Re-emit any data coming from the coalesce stream to the outside world
  4219. coalesceStream$1.on('data', function (event) {
  4220. self.trigger('data', event);
  4221. }); // Let the consumer know we have finished flushing the entire pipeline
  4222. coalesceStream$1.on('done', function () {
  4223. self.trigger('done');
  4224. });
  4225. };
  4226. _Transmuxer.prototype = new stream(); // forward compatibility
  4227. var transmuxer = _Transmuxer;
  4228. // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf.
  4229. // Technically, this function returns the header and a metadata FLV tag
  4230. // if duration is greater than zero
  4231. // duration in seconds
  4232. // @return {object} the bytes of the FLV header as a Uint8Array
  4233. var getFlvHeader = function getFlvHeader(duration, audio, video) {
  4234. // :ByteArray {
  4235. var headBytes = new Uint8Array(3 + 1 + 1 + 4),
  4236. head = new DataView(headBytes.buffer),
  4237. metadata,
  4238. result,
  4239. metadataLength; // default arguments
  4240. duration = duration || 0;
  4241. audio = audio === undefined ? true : audio;
  4242. video = video === undefined ? true : video; // signature
  4243. head.setUint8(0, 0x46); // 'F'
  4244. head.setUint8(1, 0x4c); // 'L'
  4245. head.setUint8(2, 0x56); // 'V'
  4246. // version
  4247. head.setUint8(3, 0x01); // flags
  4248. head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00)); // data offset, should be 9 for FLV v1
  4249. head.setUint32(5, headBytes.byteLength); // init the first FLV tag
  4250. if (duration <= 0) {
  4251. // no duration available so just write the first field of the first
  4252. // FLV tag
  4253. result = new Uint8Array(headBytes.byteLength + 4);
  4254. result.set(headBytes);
  4255. result.set([0, 0, 0, 0], headBytes.byteLength);
  4256. return result;
  4257. } // write out the duration metadata tag
  4258. metadata = new flvTag(flvTag.METADATA_TAG);
  4259. metadata.pts = metadata.dts = 0;
  4260. metadata.writeMetaDataDouble('duration', duration);
  4261. metadataLength = metadata.finalize().length;
  4262. result = new Uint8Array(headBytes.byteLength + metadataLength);
  4263. result.set(headBytes);
  4264. result.set(head.byteLength, metadataLength);
  4265. return result;
  4266. };
  4267. var flvHeader = getFlvHeader;
  4268. /**
  4269. * mux.js
  4270. *
  4271. * Copyright (c) Brightcove
  4272. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  4273. */
  4274. var flv = {
  4275. tag: flvTag,
  4276. Transmuxer: transmuxer,
  4277. getFlvHeader: flvHeader
  4278. };
  4279. return flv;
  4280. })));