transmuxer.js 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) 2015 Brightcove
  5. * All rights reserved.
  6. *
  7. * A stream-based mp2t to mp4 converter. This utility can be used to
  8. * deliver mp4s to a SourceBuffer on platforms that support native
  9. * Media Source Extensions.
  10. */
  11. 'use strict';
  12. var Stream = require('../utils/stream.js');
  13. var mp4 = require('./mp4-generator.js');
  14. var m2ts = require('../m2ts/m2ts.js');
  15. var AdtsStream = require('../codecs/adts.js');
  16. var H264Stream = require('../codecs/h264').H264Stream;
  17. var AacStream = require('../aac');
  18. var coneOfSilence = require('../data/silence');
  19. var clock = require('../utils/clock');
  20. // constants
  21. var AUDIO_PROPERTIES = [
  22. 'audioobjecttype',
  23. 'channelcount',
  24. 'samplerate',
  25. 'samplingfrequencyindex',
  26. 'samplesize'
  27. ];
  28. var VIDEO_PROPERTIES = [
  29. 'width',
  30. 'height',
  31. 'profileIdc',
  32. 'levelIdc',
  33. 'profileCompatibility'
  34. ];
  35. var ONE_SECOND_IN_TS = 90000; // 90kHz clock
  36. // object types
  37. var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
  38. // Helper functions
  39. var
  40. createDefaultSample,
  41. isLikelyAacData,
  42. collectDtsInfo,
  43. clearDtsInfo,
  44. calculateTrackBaseMediaDecodeTime,
  45. arrayEquals,
  46. sumFrameByteLengths;
  47. /**
  48. * Default sample object
  49. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  50. */
  51. createDefaultSample = function() {
  52. return {
  53. size: 0,
  54. flags: {
  55. isLeading: 0,
  56. dependsOn: 1,
  57. isDependedOn: 0,
  58. hasRedundancy: 0,
  59. degradationPriority: 0
  60. }
  61. };
  62. };
  63. isLikelyAacData = function(data) {
  64. if ((data[0] === 'I'.charCodeAt(0)) &&
  65. (data[1] === 'D'.charCodeAt(0)) &&
  66. (data[2] === '3'.charCodeAt(0))) {
  67. return true;
  68. }
  69. return false;
  70. };
  71. /**
  72. * Compare two arrays (even typed) for same-ness
  73. */
  74. arrayEquals = function(a, b) {
  75. var
  76. i;
  77. if (a.length !== b.length) {
  78. return false;
  79. }
  80. // compare the value of each element in the array
  81. for (i = 0; i < a.length; i++) {
  82. if (a[i] !== b[i]) {
  83. return false;
  84. }
  85. }
  86. return true;
  87. };
  88. /**
  89. * Sum the `byteLength` properties of the data in each AAC frame
  90. */
  91. sumFrameByteLengths = function(array) {
  92. var
  93. i,
  94. currentObj,
  95. sum = 0;
  96. // sum the byteLength's all each nal unit in the frame
  97. for (i = 0; i < array.length; i++) {
  98. currentObj = array[i];
  99. sum += currentObj.data.byteLength;
  100. }
  101. return sum;
  102. };
  103. /**
  104. * Constructs a single-track, ISO BMFF media segment from AAC data
  105. * events. The output of this stream can be fed to a SourceBuffer
  106. * configured with a suitable initialization segment.
  107. */
  108. AudioSegmentStream = function(track) {
  109. var
  110. adtsFrames = [],
  111. sequenceNumber = 0,
  112. earliestAllowedDts = 0,
  113. audioAppendStartTs = 0,
  114. videoBaseMediaDecodeTime = Infinity;
  115. AudioSegmentStream.prototype.init.call(this);
  116. this.push = function(data) {
  117. collectDtsInfo(track, data);
  118. if (track) {
  119. AUDIO_PROPERTIES.forEach(function(prop) {
  120. track[prop] = data[prop];
  121. });
  122. }
  123. // buffer audio data until end() is called
  124. adtsFrames.push(data);
  125. };
  126. this.setEarliestDts = function(earliestDts) {
  127. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  128. };
  129. this.setVideoBaseMediaDecodeTime = function(baseMediaDecodeTime) {
  130. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  131. };
  132. this.setAudioAppendStart = function(timestamp) {
  133. audioAppendStartTs = timestamp;
  134. };
  135. this.flush = function() {
  136. var
  137. frames,
  138. moof,
  139. mdat,
  140. boxes;
  141. // return early if no audio data has been observed
  142. if (adtsFrames.length === 0) {
  143. this.trigger('done', 'AudioSegmentStream');
  144. return;
  145. }
  146. frames = this.trimAdtsFramesByEarliestDts_(adtsFrames);
  147. track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track);
  148. this.prefixWithSilence_(track, frames);
  149. // we have to build the index from byte locations to
  150. // samples (that is, adts frames) in the audio data
  151. track.samples = this.generateSampleTable_(frames);
  152. // concatenate the audio data to constuct the mdat
  153. mdat = mp4.mdat(this.concatenateFrameData_(frames));
  154. adtsFrames = [];
  155. moof = mp4.moof(sequenceNumber, [track]);
  156. boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
  157. // bump the sequence number for next time
  158. sequenceNumber++;
  159. boxes.set(moof);
  160. boxes.set(mdat, moof.byteLength);
  161. clearDtsInfo(track);
  162. this.trigger('data', {track: track, boxes: boxes});
  163. this.trigger('done', 'AudioSegmentStream');
  164. };
  165. // Possibly pad (prefix) the audio track with silence if appending this track
  166. // would lead to the introduction of a gap in the audio buffer
  167. this.prefixWithSilence_ = function(track, frames) {
  168. var
  169. baseMediaDecodeTimeTs,
  170. frameDuration = 0,
  171. audioGapDuration = 0,
  172. audioFillFrameCount = 0,
  173. audioFillDuration = 0,
  174. silentFrame,
  175. i;
  176. if (!frames.length) {
  177. return;
  178. }
  179. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate);
  180. // determine frame clock duration based on sample rate, round up to avoid overfills
  181. frameDuration = Math.ceil(ONE_SECOND_IN_TS / (track.samplerate / 1024));
  182. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  183. // insert the shortest possible amount (audio gap or audio to video gap)
  184. audioGapDuration =
  185. baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime);
  186. // number of full frames in the audio gap
  187. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  188. audioFillDuration = audioFillFrameCount * frameDuration;
  189. }
  190. // don't attempt to fill gaps smaller than a single frame or larger
  191. // than a half second
  192. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS / 2) {
  193. return;
  194. }
  195. silentFrame = coneOfSilence[track.samplerate];
  196. if (!silentFrame) {
  197. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  198. // from the content instead
  199. silentFrame = frames[0].data;
  200. }
  201. for (i = 0; i < audioFillFrameCount; i++) {
  202. frames.splice(i, 0, {
  203. data: silentFrame
  204. });
  205. }
  206. track.baseMediaDecodeTime -=
  207. Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  208. };
  209. // If the audio segment extends before the earliest allowed dts
  210. // value, remove AAC frames until starts at or after the earliest
  211. // allowed DTS so that we don't end up with a negative baseMedia-
  212. // DecodeTime for the audio track
  213. this.trimAdtsFramesByEarliestDts_ = function(adtsFrames) {
  214. if (track.minSegmentDts >= earliestAllowedDts) {
  215. return adtsFrames;
  216. }
  217. // We will need to recalculate the earliest segment Dts
  218. track.minSegmentDts = Infinity;
  219. return adtsFrames.filter(function(currentFrame) {
  220. // If this is an allowed frame, keep it and record it's Dts
  221. if (currentFrame.dts >= earliestAllowedDts) {
  222. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  223. track.minSegmentPts = track.minSegmentDts;
  224. return true;
  225. }
  226. // Otherwise, discard it
  227. return false;
  228. });
  229. };
  230. // generate the track's raw mdat data from an array of frames
  231. this.generateSampleTable_ = function(frames) {
  232. var
  233. i,
  234. currentFrame,
  235. samples = [];
  236. for (i = 0; i < frames.length; i++) {
  237. currentFrame = frames[i];
  238. samples.push({
  239. size: currentFrame.data.byteLength,
  240. duration: 1024 // For AAC audio, all samples contain 1024 samples
  241. });
  242. }
  243. return samples;
  244. };
  245. // generate the track's sample table from an array of frames
  246. this.concatenateFrameData_ = function(frames) {
  247. var
  248. i,
  249. currentFrame,
  250. dataOffset = 0,
  251. data = new Uint8Array(sumFrameByteLengths(frames));
  252. for (i = 0; i < frames.length; i++) {
  253. currentFrame = frames[i];
  254. data.set(currentFrame.data, dataOffset);
  255. dataOffset += currentFrame.data.byteLength;
  256. }
  257. return data;
  258. };
  259. };
  260. AudioSegmentStream.prototype = new Stream();
  261. /**
  262. * Constructs a single-track, ISO BMFF media segment from H264 data
  263. * events. The output of this stream can be fed to a SourceBuffer
  264. * configured with a suitable initialization segment.
  265. * @param track {object} track metadata configuration
  266. * @param options {object} transmuxer options object
  267. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  268. * gopsToAlignWith list when attempting to align gop pts
  269. */
  270. VideoSegmentStream = function(track, options) {
  271. var
  272. sequenceNumber = 0,
  273. nalUnits = [],
  274. gopsToAlignWith = [],
  275. config,
  276. pps;
  277. options = options || {};
  278. VideoSegmentStream.prototype.init.call(this);
  279. delete track.minPTS;
  280. this.gopCache_ = [];
  281. this.push = function(nalUnit) {
  282. collectDtsInfo(track, nalUnit);
  283. // record the track config
  284. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  285. config = nalUnit.config;
  286. track.sps = [nalUnit.data];
  287. VIDEO_PROPERTIES.forEach(function(prop) {
  288. track[prop] = config[prop];
  289. }, this);
  290. }
  291. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' &&
  292. !pps) {
  293. pps = nalUnit.data;
  294. track.pps = [nalUnit.data];
  295. }
  296. // buffer video until flush() is called
  297. nalUnits.push(nalUnit);
  298. };
  299. this.flush = function() {
  300. var
  301. frames,
  302. gopForFusion,
  303. gops,
  304. moof,
  305. mdat,
  306. boxes;
  307. // Throw away nalUnits at the start of the byte stream until
  308. // we find the first AUD
  309. while (nalUnits.length) {
  310. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  311. break;
  312. }
  313. nalUnits.shift();
  314. }
  315. // Return early if no video data has been observed
  316. if (nalUnits.length === 0) {
  317. this.resetStream_();
  318. this.trigger('done', 'VideoSegmentStream');
  319. return;
  320. }
  321. // Organize the raw nal-units into arrays that represent
  322. // higher-level constructs such as frames and gops
  323. // (group-of-pictures)
  324. frames = this.groupNalsIntoFrames_(nalUnits);
  325. gops = this.groupFramesIntoGops_(frames);
  326. // If the first frame of this fragment is not a keyframe we have
  327. // a problem since MSE (on Chrome) requires a leading keyframe.
  328. //
  329. // We have two approaches to repairing this situation:
  330. // 1) GOP-FUSION:
  331. // This is where we keep track of the GOPS (group-of-pictures)
  332. // from previous fragments and attempt to find one that we can
  333. // prepend to the current fragment in order to create a valid
  334. // fragment.
  335. // 2) KEYFRAME-PULLING:
  336. // Here we search for the first keyframe in the fragment and
  337. // throw away all the frames between the start of the fragment
  338. // and that keyframe. We then extend the duration and pull the
  339. // PTS of the keyframe forward so that it covers the time range
  340. // of the frames that were disposed of.
  341. //
  342. // #1 is far prefereable over #2 which can cause "stuttering" but
  343. // requires more things to be just right.
  344. if (!gops[0][0].keyFrame) {
  345. // Search for a gop for fusion from our gopCache
  346. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  347. if (gopForFusion) {
  348. gops.unshift(gopForFusion);
  349. // Adjust Gops' metadata to account for the inclusion of the
  350. // new gop at the beginning
  351. gops.byteLength += gopForFusion.byteLength;
  352. gops.nalCount += gopForFusion.nalCount;
  353. gops.pts = gopForFusion.pts;
  354. gops.dts = gopForFusion.dts;
  355. gops.duration += gopForFusion.duration;
  356. } else {
  357. // If we didn't find a candidate gop fall back to keyrame-pulling
  358. gops = this.extendFirstKeyFrame_(gops);
  359. }
  360. }
  361. // Trim gops to align with gopsToAlignWith
  362. if (gopsToAlignWith.length) {
  363. var alignedGops;
  364. if (options.alignGopsAtEnd) {
  365. alignedGops = this.alignGopsAtEnd_(gops);
  366. } else {
  367. alignedGops = this.alignGopsAtStart_(gops);
  368. }
  369. if (!alignedGops) {
  370. // save all the nals in the last GOP into the gop cache
  371. this.gopCache_.unshift({
  372. gop: gops.pop(),
  373. pps: track.pps,
  374. sps: track.sps
  375. });
  376. // Keep a maximum of 6 GOPs in the cache
  377. this.gopCache_.length = Math.min(6, this.gopCache_.length);
  378. // Clear nalUnits
  379. nalUnits = [];
  380. // return early no gops can be aligned with desired gopsToAlignWith
  381. this.resetStream_();
  382. this.trigger('done', 'VideoSegmentStream');
  383. return;
  384. }
  385. // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  386. // when recalculated before sending off to CoalesceStream
  387. clearDtsInfo(track);
  388. gops = alignedGops;
  389. }
  390. collectDtsInfo(track, gops);
  391. // First, we have to build the index from byte locations to
  392. // samples (that is, frames) in the video data
  393. track.samples = this.generateSampleTable_(gops);
  394. // Concatenate the video data and construct the mdat
  395. mdat = mp4.mdat(this.concatenateNalData_(gops));
  396. track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track);
  397. this.trigger('processedGopsInfo', gops.map(function(gop) {
  398. return {
  399. pts: gop.pts,
  400. dts: gop.dts,
  401. byteLength: gop.byteLength
  402. };
  403. }));
  404. // save all the nals in the last GOP into the gop cache
  405. this.gopCache_.unshift({
  406. gop: gops.pop(),
  407. pps: track.pps,
  408. sps: track.sps
  409. });
  410. // Keep a maximum of 6 GOPs in the cache
  411. this.gopCache_.length = Math.min(6, this.gopCache_.length);
  412. // Clear nalUnits
  413. nalUnits = [];
  414. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  415. this.trigger('timelineStartInfo', track.timelineStartInfo);
  416. moof = mp4.moof(sequenceNumber, [track]);
  417. // it would be great to allocate this array up front instead of
  418. // throwing away hundreds of media segment fragments
  419. boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
  420. // Bump the sequence number for next time
  421. sequenceNumber++;
  422. boxes.set(moof);
  423. boxes.set(mdat, moof.byteLength);
  424. this.trigger('data', {track: track, boxes: boxes});
  425. this.resetStream_();
  426. // Continue with the flush process now
  427. this.trigger('done', 'VideoSegmentStream');
  428. };
  429. this.resetStream_ = function() {
  430. clearDtsInfo(track);
  431. // reset config and pps because they may differ across segments
  432. // for instance, when we are rendition switching
  433. config = undefined;
  434. pps = undefined;
  435. };
  436. // Search for a candidate Gop for gop-fusion from the gop cache and
  437. // return it or return null if no good candidate was found
  438. this.getGopForFusion_ = function(nalUnit) {
  439. var
  440. halfSecond = 45000, // Half-a-second in a 90khz clock
  441. allowableOverlap = 10000, // About 3 frames @ 30fps
  442. nearestDistance = Infinity,
  443. dtsDistance,
  444. nearestGopObj,
  445. currentGop,
  446. currentGopObj,
  447. i;
  448. // Search for the GOP nearest to the beginning of this nal unit
  449. for (i = 0; i < this.gopCache_.length; i++) {
  450. currentGopObj = this.gopCache_[i];
  451. currentGop = currentGopObj.gop;
  452. // Reject Gops with different SPS or PPS
  453. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) ||
  454. !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  455. continue;
  456. }
  457. // Reject Gops that would require a negative baseMediaDecodeTime
  458. if (currentGop.dts < track.timelineStartInfo.dts) {
  459. continue;
  460. }
  461. // The distance between the end of the gop and the start of the nalUnit
  462. dtsDistance = (nalUnit.dts - currentGop.dts) - currentGop.duration;
  463. // Only consider GOPS that start before the nal unit and end within
  464. // a half-second of the nal unit
  465. if (dtsDistance >= -allowableOverlap &&
  466. dtsDistance <= halfSecond) {
  467. // Always use the closest GOP we found if there is more than
  468. // one candidate
  469. if (!nearestGopObj ||
  470. nearestDistance > dtsDistance) {
  471. nearestGopObj = currentGopObj;
  472. nearestDistance = dtsDistance;
  473. }
  474. }
  475. }
  476. if (nearestGopObj) {
  477. return nearestGopObj.gop;
  478. }
  479. return null;
  480. };
  481. this.extendFirstKeyFrame_ = function(gops) {
  482. var currentGop;
  483. if (!gops[0][0].keyFrame && gops.length > 1) {
  484. // Remove the first GOP
  485. currentGop = gops.shift();
  486. gops.byteLength -= currentGop.byteLength;
  487. gops.nalCount -= currentGop.nalCount;
  488. // Extend the first frame of what is now the
  489. // first gop to cover the time period of the
  490. // frames we just removed
  491. gops[0][0].dts = currentGop.dts;
  492. gops[0][0].pts = currentGop.pts;
  493. gops[0][0].duration += currentGop.duration;
  494. }
  495. return gops;
  496. };
  497. // Convert an array of nal units into an array of frames with each frame being
  498. // composed of the nal units that make up that frame
  499. // Also keep track of cummulative data about the frame from the nal units such
  500. // as the frame duration, starting pts, etc.
  501. this.groupNalsIntoFrames_ = function(nalUnits) {
  502. var
  503. i,
  504. currentNal,
  505. currentFrame = [],
  506. frames = [];
  507. currentFrame.byteLength = 0;
  508. for (i = 0; i < nalUnits.length; i++) {
  509. currentNal = nalUnits[i];
  510. // Split on 'aud'-type nal units
  511. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  512. // Since the very first nal unit is expected to be an AUD
  513. // only push to the frames array when currentFrame is not empty
  514. if (currentFrame.length) {
  515. currentFrame.duration = currentNal.dts - currentFrame.dts;
  516. frames.push(currentFrame);
  517. }
  518. currentFrame = [currentNal];
  519. currentFrame.byteLength = currentNal.data.byteLength;
  520. currentFrame.pts = currentNal.pts;
  521. currentFrame.dts = currentNal.dts;
  522. } else {
  523. // Specifically flag key frames for ease of use later
  524. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  525. currentFrame.keyFrame = true;
  526. }
  527. currentFrame.duration = currentNal.dts - currentFrame.dts;
  528. currentFrame.byteLength += currentNal.data.byteLength;
  529. currentFrame.push(currentNal);
  530. }
  531. }
  532. // For the last frame, use the duration of the previous frame if we
  533. // have nothing better to go on
  534. if (frames.length &&
  535. (!currentFrame.duration ||
  536. currentFrame.duration <= 0)) {
  537. currentFrame.duration = frames[frames.length - 1].duration;
  538. }
  539. // Push the final frame
  540. frames.push(currentFrame);
  541. return frames;
  542. };
  543. // Convert an array of frames into an array of Gop with each Gop being composed
  544. // of the frames that make up that Gop
  545. // Also keep track of cummulative data about the Gop from the frames such as the
  546. // Gop duration, starting pts, etc.
  547. this.groupFramesIntoGops_ = function(frames) {
  548. var
  549. i,
  550. currentFrame,
  551. currentGop = [],
  552. gops = [];
  553. // We must pre-set some of the values on the Gop since we
  554. // keep running totals of these values
  555. currentGop.byteLength = 0;
  556. currentGop.nalCount = 0;
  557. currentGop.duration = 0;
  558. currentGop.pts = frames[0].pts;
  559. currentGop.dts = frames[0].dts;
  560. // store some metadata about all the Gops
  561. gops.byteLength = 0;
  562. gops.nalCount = 0;
  563. gops.duration = 0;
  564. gops.pts = frames[0].pts;
  565. gops.dts = frames[0].dts;
  566. for (i = 0; i < frames.length; i++) {
  567. currentFrame = frames[i];
  568. if (currentFrame.keyFrame) {
  569. // Since the very first frame is expected to be an keyframe
  570. // only push to the gops array when currentGop is not empty
  571. if (currentGop.length) {
  572. gops.push(currentGop);
  573. gops.byteLength += currentGop.byteLength;
  574. gops.nalCount += currentGop.nalCount;
  575. gops.duration += currentGop.duration;
  576. }
  577. currentGop = [currentFrame];
  578. currentGop.nalCount = currentFrame.length;
  579. currentGop.byteLength = currentFrame.byteLength;
  580. currentGop.pts = currentFrame.pts;
  581. currentGop.dts = currentFrame.dts;
  582. currentGop.duration = currentFrame.duration;
  583. } else {
  584. currentGop.duration += currentFrame.duration;
  585. currentGop.nalCount += currentFrame.length;
  586. currentGop.byteLength += currentFrame.byteLength;
  587. currentGop.push(currentFrame);
  588. }
  589. }
  590. if (gops.length && currentGop.duration <= 0) {
  591. currentGop.duration = gops[gops.length - 1].duration;
  592. }
  593. gops.byteLength += currentGop.byteLength;
  594. gops.nalCount += currentGop.nalCount;
  595. gops.duration += currentGop.duration;
  596. // push the final Gop
  597. gops.push(currentGop);
  598. return gops;
  599. };
  600. // generate the track's sample table from an array of gops
  601. this.generateSampleTable_ = function(gops, baseDataOffset) {
  602. var
  603. h, i,
  604. sample,
  605. currentGop,
  606. currentFrame,
  607. dataOffset = baseDataOffset || 0,
  608. samples = [];
  609. for (h = 0; h < gops.length; h++) {
  610. currentGop = gops[h];
  611. for (i = 0; i < currentGop.length; i++) {
  612. currentFrame = currentGop[i];
  613. sample = createDefaultSample();
  614. sample.dataOffset = dataOffset;
  615. sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts;
  616. sample.duration = currentFrame.duration;
  617. sample.size = 4 * currentFrame.length; // Space for nal unit size
  618. sample.size += currentFrame.byteLength;
  619. if (currentFrame.keyFrame) {
  620. sample.flags.dependsOn = 2;
  621. }
  622. dataOffset += sample.size;
  623. samples.push(sample);
  624. }
  625. }
  626. return samples;
  627. };
  628. // generate the track's raw mdat data from an array of gops
  629. this.concatenateNalData_ = function(gops) {
  630. var
  631. h, i, j,
  632. currentGop,
  633. currentFrame,
  634. currentNal,
  635. dataOffset = 0,
  636. nalsByteLength = gops.byteLength,
  637. numberOfNals = gops.nalCount,
  638. totalByteLength = nalsByteLength + 4 * numberOfNals,
  639. data = new Uint8Array(totalByteLength),
  640. view = new DataView(data.buffer);
  641. // For each Gop..
  642. for (h = 0; h < gops.length; h++) {
  643. currentGop = gops[h];
  644. // For each Frame..
  645. for (i = 0; i < currentGop.length; i++) {
  646. currentFrame = currentGop[i];
  647. // For each NAL..
  648. for (j = 0; j < currentFrame.length; j++) {
  649. currentNal = currentFrame[j];
  650. view.setUint32(dataOffset, currentNal.data.byteLength);
  651. dataOffset += 4;
  652. data.set(currentNal.data, dataOffset);
  653. dataOffset += currentNal.data.byteLength;
  654. }
  655. }
  656. }
  657. return data;
  658. };
  659. // trim gop list to the first gop found that has a matching pts with a gop in the list
  660. // of gopsToAlignWith starting from the START of the list
  661. this.alignGopsAtStart_ = function(gops) {
  662. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  663. byteLength = gops.byteLength;
  664. nalCount = gops.nalCount;
  665. duration = gops.duration;
  666. alignIndex = gopIndex = 0;
  667. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  668. align = gopsToAlignWith[alignIndex];
  669. gop = gops[gopIndex];
  670. if (align.pts === gop.pts) {
  671. break;
  672. }
  673. if (gop.pts > align.pts) {
  674. // this current gop starts after the current gop we want to align on, so increment
  675. // align index
  676. alignIndex++;
  677. continue;
  678. }
  679. // current gop starts before the current gop we want to align on. so increment gop
  680. // index
  681. gopIndex++;
  682. byteLength -= gop.byteLength;
  683. nalCount -= gop.nalCount;
  684. duration -= gop.duration;
  685. }
  686. if (gopIndex === 0) {
  687. // no gops to trim
  688. return gops;
  689. }
  690. if (gopIndex === gops.length) {
  691. // all gops trimmed, skip appending all gops
  692. return null;
  693. }
  694. alignedGops = gops.slice(gopIndex);
  695. alignedGops.byteLength = byteLength;
  696. alignedGops.duration = duration;
  697. alignedGops.nalCount = nalCount;
  698. alignedGops.pts = alignedGops[0].pts;
  699. alignedGops.dts = alignedGops[0].dts;
  700. return alignedGops;
  701. };
  702. // trim gop list to the first gop found that has a matching pts with a gop in the list
  703. // of gopsToAlignWith starting from the END of the list
  704. this.alignGopsAtEnd_ = function(gops) {
  705. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  706. alignIndex = gopsToAlignWith.length - 1;
  707. gopIndex = gops.length - 1;
  708. alignEndIndex = null;
  709. matchFound = false;
  710. while (alignIndex >= 0 && gopIndex >= 0) {
  711. align = gopsToAlignWith[alignIndex];
  712. gop = gops[gopIndex];
  713. if (align.pts === gop.pts) {
  714. matchFound = true;
  715. break;
  716. }
  717. if (align.pts > gop.pts) {
  718. alignIndex--;
  719. continue;
  720. }
  721. if (alignIndex === gopsToAlignWith.length - 1) {
  722. // gop.pts is greater than the last alignment candidate. If no match is found
  723. // by the end of this loop, we still want to append gops that come after this
  724. // point
  725. alignEndIndex = gopIndex;
  726. }
  727. gopIndex--;
  728. }
  729. if (!matchFound && alignEndIndex === null) {
  730. return null;
  731. }
  732. var trimIndex;
  733. if (matchFound) {
  734. trimIndex = gopIndex;
  735. } else {
  736. trimIndex = alignEndIndex;
  737. }
  738. if (trimIndex === 0) {
  739. return gops;
  740. }
  741. var alignedGops = gops.slice(trimIndex);
  742. var metadata = alignedGops.reduce(function(total, gop) {
  743. total.byteLength += gop.byteLength;
  744. total.duration += gop.duration;
  745. total.nalCount += gop.nalCount;
  746. return total;
  747. }, { byteLength: 0, duration: 0, nalCount: 0 });
  748. alignedGops.byteLength = metadata.byteLength;
  749. alignedGops.duration = metadata.duration;
  750. alignedGops.nalCount = metadata.nalCount;
  751. alignedGops.pts = alignedGops[0].pts;
  752. alignedGops.dts = alignedGops[0].dts;
  753. return alignedGops;
  754. };
  755. this.alignGopsWith = function(newGopsToAlignWith) {
  756. gopsToAlignWith = newGopsToAlignWith;
  757. };
  758. };
  759. VideoSegmentStream.prototype = new Stream();
  760. /**
  761. * Store information about the start and end of the track and the
  762. * duration for each frame/sample we process in order to calculate
  763. * the baseMediaDecodeTime
  764. */
  765. collectDtsInfo = function(track, data) {
  766. if (typeof data.pts === 'number') {
  767. if (track.timelineStartInfo.pts === undefined) {
  768. track.timelineStartInfo.pts = data.pts;
  769. }
  770. if (track.minSegmentPts === undefined) {
  771. track.minSegmentPts = data.pts;
  772. } else {
  773. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  774. }
  775. if (track.maxSegmentPts === undefined) {
  776. track.maxSegmentPts = data.pts;
  777. } else {
  778. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  779. }
  780. }
  781. if (typeof data.dts === 'number') {
  782. if (track.timelineStartInfo.dts === undefined) {
  783. track.timelineStartInfo.dts = data.dts;
  784. }
  785. if (track.minSegmentDts === undefined) {
  786. track.minSegmentDts = data.dts;
  787. } else {
  788. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  789. }
  790. if (track.maxSegmentDts === undefined) {
  791. track.maxSegmentDts = data.dts;
  792. } else {
  793. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  794. }
  795. }
  796. };
  797. /**
  798. * Clear values used to calculate the baseMediaDecodeTime between
  799. * tracks
  800. */
  801. clearDtsInfo = function(track) {
  802. delete track.minSegmentDts;
  803. delete track.maxSegmentDts;
  804. delete track.minSegmentPts;
  805. delete track.maxSegmentPts;
  806. };
  807. /**
  808. * Calculate the track's baseMediaDecodeTime based on the earliest
  809. * DTS the transmuxer has ever seen and the minimum DTS for the
  810. * current track
  811. */
  812. calculateTrackBaseMediaDecodeTime = function(track) {
  813. var
  814. baseMediaDecodeTime,
  815. scale,
  816. // Calculate the distance, in time, that this segment starts from the start
  817. // of the timeline (earliest time seen since the transmuxer initialized)
  818. timeSinceStartOfTimeline = track.minSegmentDts - track.timelineStartInfo.dts;
  819. // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  820. // we want the start of the first segment to be placed
  821. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime;
  822. // Add to that the distance this segment is from the very first
  823. baseMediaDecodeTime += timeSinceStartOfTimeline;
  824. // baseMediaDecodeTime must not become negative
  825. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  826. if (track.type === 'audio') {
  827. // Audio has a different clock equal to the sampling_rate so we need to
  828. // scale the PTS values into the clock rate of the track
  829. scale = track.samplerate / ONE_SECOND_IN_TS;
  830. baseMediaDecodeTime *= scale;
  831. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  832. }
  833. return baseMediaDecodeTime;
  834. };
  835. /**
  836. * A Stream that can combine multiple streams (ie. audio & video)
  837. * into a single output segment for MSE. Also supports audio-only
  838. * and video-only streams.
  839. */
  840. CoalesceStream = function(options, metadataStream) {
  841. // Number of Tracks per output segment
  842. // If greater than 1, we combine multiple
  843. // tracks into a single segment
  844. this.numberOfTracks = 0;
  845. this.metadataStream = metadataStream;
  846. if (typeof options.remux !== 'undefined') {
  847. this.remuxTracks = !!options.remux;
  848. } else {
  849. this.remuxTracks = true;
  850. }
  851. this.pendingTracks = [];
  852. this.videoTrack = null;
  853. this.pendingBoxes = [];
  854. this.pendingCaptions = [];
  855. this.pendingMetadata = [];
  856. this.pendingBytes = 0;
  857. this.emittedTracks = 0;
  858. CoalesceStream.prototype.init.call(this);
  859. // Take output from multiple
  860. this.push = function(output) {
  861. // buffer incoming captions until the associated video segment
  862. // finishes
  863. if (output.text) {
  864. return this.pendingCaptions.push(output);
  865. }
  866. // buffer incoming id3 tags until the final flush
  867. if (output.frames) {
  868. return this.pendingMetadata.push(output);
  869. }
  870. // Add this track to the list of pending tracks and store
  871. // important information required for the construction of
  872. // the final segment
  873. this.pendingTracks.push(output.track);
  874. this.pendingBoxes.push(output.boxes);
  875. this.pendingBytes += output.boxes.byteLength;
  876. if (output.track.type === 'video') {
  877. this.videoTrack = output.track;
  878. }
  879. if (output.track.type === 'audio') {
  880. this.audioTrack = output.track;
  881. }
  882. };
  883. };
  884. CoalesceStream.prototype = new Stream();
  885. CoalesceStream.prototype.flush = function(flushSource) {
  886. var
  887. offset = 0,
  888. event = {
  889. captions: [],
  890. captionStreams: {},
  891. metadata: [],
  892. info: {}
  893. },
  894. caption,
  895. id3,
  896. initSegment,
  897. timelineStartPts = 0,
  898. i;
  899. if (this.pendingTracks.length < this.numberOfTracks) {
  900. if (flushSource !== 'VideoSegmentStream' &&
  901. flushSource !== 'AudioSegmentStream') {
  902. // Return because we haven't received a flush from a data-generating
  903. // portion of the segment (meaning that we have only recieved meta-data
  904. // or captions.)
  905. return;
  906. } else if (this.remuxTracks) {
  907. // Return until we have enough tracks from the pipeline to remux (if we
  908. // are remuxing audio and video into a single MP4)
  909. return;
  910. } else if (this.pendingTracks.length === 0) {
  911. // In the case where we receive a flush without any data having been
  912. // received we consider it an emitted track for the purposes of coalescing
  913. // `done` events.
  914. // We do this for the case where there is an audio and video track in the
  915. // segment but no audio data. (seen in several playlists with alternate
  916. // audio tracks and no audio present in the main TS segments.)
  917. this.emittedTracks++;
  918. if (this.emittedTracks >= this.numberOfTracks) {
  919. this.trigger('done');
  920. this.emittedTracks = 0;
  921. }
  922. return;
  923. }
  924. }
  925. if (this.videoTrack) {
  926. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  927. VIDEO_PROPERTIES.forEach(function(prop) {
  928. event.info[prop] = this.videoTrack[prop];
  929. }, this);
  930. } else if (this.audioTrack) {
  931. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  932. AUDIO_PROPERTIES.forEach(function(prop) {
  933. event.info[prop] = this.audioTrack[prop];
  934. }, this);
  935. }
  936. if (this.pendingTracks.length === 1) {
  937. event.type = this.pendingTracks[0].type;
  938. } else {
  939. event.type = 'combined';
  940. }
  941. this.emittedTracks += this.pendingTracks.length;
  942. initSegment = mp4.initSegment(this.pendingTracks);
  943. // Create a new typed array to hold the init segment
  944. event.initSegment = new Uint8Array(initSegment.byteLength);
  945. // Create an init segment containing a moov
  946. // and track definitions
  947. event.initSegment.set(initSegment);
  948. // Create a new typed array to hold the moof+mdats
  949. event.data = new Uint8Array(this.pendingBytes);
  950. // Append each moof+mdat (one per track) together
  951. for (i = 0; i < this.pendingBoxes.length; i++) {
  952. event.data.set(this.pendingBoxes[i], offset);
  953. offset += this.pendingBoxes[i].byteLength;
  954. }
  955. // Translate caption PTS times into second offsets into the
  956. // video timeline for the segment, and add track info
  957. for (i = 0; i < this.pendingCaptions.length; i++) {
  958. caption = this.pendingCaptions[i];
  959. caption.startTime = (caption.startPts - timelineStartPts);
  960. caption.startTime /= 90e3;
  961. caption.endTime = (caption.endPts - timelineStartPts);
  962. caption.endTime /= 90e3;
  963. event.captionStreams[caption.stream] = true;
  964. event.captions.push(caption);
  965. }
  966. // Translate ID3 frame PTS times into second offsets into the
  967. // video timeline for the segment
  968. for (i = 0; i < this.pendingMetadata.length; i++) {
  969. id3 = this.pendingMetadata[i];
  970. id3.cueTime = (id3.pts - timelineStartPts);
  971. id3.cueTime /= 90e3;
  972. event.metadata.push(id3);
  973. }
  974. // We add this to every single emitted segment even though we only need
  975. // it for the first
  976. event.metadata.dispatchType = this.metadataStream.dispatchType;
  977. // Reset stream state
  978. this.pendingTracks.length = 0;
  979. this.videoTrack = null;
  980. this.pendingBoxes.length = 0;
  981. this.pendingCaptions.length = 0;
  982. this.pendingBytes = 0;
  983. this.pendingMetadata.length = 0;
  984. // Emit the built segment
  985. this.trigger('data', event);
  986. // Only emit `done` if all tracks have been flushed and emitted
  987. if (this.emittedTracks >= this.numberOfTracks) {
  988. this.trigger('done');
  989. this.emittedTracks = 0;
  990. }
  991. };
  992. /**
  993. * A Stream that expects MP2T binary data as input and produces
  994. * corresponding media segments, suitable for use with Media Source
  995. * Extension (MSE) implementations that support the ISO BMFF byte
  996. * stream format, like Chrome.
  997. */
  998. Transmuxer = function(options) {
  999. var
  1000. self = this,
  1001. hasFlushed = true,
  1002. videoTrack,
  1003. audioTrack;
  1004. Transmuxer.prototype.init.call(this);
  1005. options = options || {};
  1006. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  1007. this.transmuxPipeline_ = {};
  1008. this.setupAacPipeline = function() {
  1009. var pipeline = {};
  1010. this.transmuxPipeline_ = pipeline;
  1011. pipeline.type = 'aac';
  1012. pipeline.metadataStream = new m2ts.MetadataStream();
  1013. // set up the parsing pipeline
  1014. pipeline.aacStream = new AacStream();
  1015. pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio');
  1016. pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata');
  1017. pipeline.adtsStream = new AdtsStream();
  1018. pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
  1019. pipeline.headOfPipeline = pipeline.aacStream;
  1020. pipeline.aacStream
  1021. .pipe(pipeline.audioTimestampRolloverStream)
  1022. .pipe(pipeline.adtsStream);
  1023. pipeline.aacStream
  1024. .pipe(pipeline.timedMetadataTimestampRolloverStream)
  1025. .pipe(pipeline.metadataStream)
  1026. .pipe(pipeline.coalesceStream);
  1027. pipeline.metadataStream.on('timestamp', function(frame) {
  1028. pipeline.aacStream.setTimestamp(frame.timeStamp);
  1029. });
  1030. pipeline.aacStream.on('data', function(data) {
  1031. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  1032. audioTrack = audioTrack || {
  1033. timelineStartInfo: {
  1034. baseMediaDecodeTime: self.baseMediaDecodeTime
  1035. },
  1036. codec: 'adts',
  1037. type: 'audio'
  1038. };
  1039. // hook up the audio segment stream to the first track with aac data
  1040. pipeline.coalesceStream.numberOfTracks++;
  1041. pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack);
  1042. // Set up the final part of the audio pipeline
  1043. pipeline.adtsStream
  1044. .pipe(pipeline.audioSegmentStream)
  1045. .pipe(pipeline.coalesceStream);
  1046. }
  1047. });
  1048. // Re-emit any data coming from the coalesce stream to the outside world
  1049. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
  1050. // Let the consumer know we have finished flushing the entire pipeline
  1051. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  1052. };
  1053. this.setupTsPipeline = function() {
  1054. var pipeline = {};
  1055. this.transmuxPipeline_ = pipeline;
  1056. pipeline.type = 'ts';
  1057. pipeline.metadataStream = new m2ts.MetadataStream();
  1058. // set up the parsing pipeline
  1059. pipeline.packetStream = new m2ts.TransportPacketStream();
  1060. pipeline.parseStream = new m2ts.TransportParseStream();
  1061. pipeline.elementaryStream = new m2ts.ElementaryStream();
  1062. pipeline.videoTimestampRolloverStream = new m2ts.TimestampRolloverStream('video');
  1063. pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio');
  1064. pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata');
  1065. pipeline.adtsStream = new AdtsStream();
  1066. pipeline.h264Stream = new H264Stream();
  1067. pipeline.captionStream = new m2ts.CaptionStream();
  1068. pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
  1069. pipeline.headOfPipeline = pipeline.packetStream;
  1070. // disassemble MPEG2-TS packets into elementary streams
  1071. pipeline.packetStream
  1072. .pipe(pipeline.parseStream)
  1073. .pipe(pipeline.elementaryStream);
  1074. // !!THIS ORDER IS IMPORTANT!!
  1075. // demux the streams
  1076. pipeline.elementaryStream
  1077. .pipe(pipeline.videoTimestampRolloverStream)
  1078. .pipe(pipeline.h264Stream);
  1079. pipeline.elementaryStream
  1080. .pipe(pipeline.audioTimestampRolloverStream)
  1081. .pipe(pipeline.adtsStream);
  1082. pipeline.elementaryStream
  1083. .pipe(pipeline.timedMetadataTimestampRolloverStream)
  1084. .pipe(pipeline.metadataStream)
  1085. .pipe(pipeline.coalesceStream);
  1086. // Hook up CEA-608/708 caption stream
  1087. pipeline.h264Stream.pipe(pipeline.captionStream)
  1088. .pipe(pipeline.coalesceStream);
  1089. pipeline.elementaryStream.on('data', function(data) {
  1090. var i;
  1091. if (data.type === 'metadata') {
  1092. i = data.tracks.length;
  1093. // scan the tracks listed in the metadata
  1094. while (i--) {
  1095. if (!videoTrack && data.tracks[i].type === 'video') {
  1096. videoTrack = data.tracks[i];
  1097. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  1098. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  1099. audioTrack = data.tracks[i];
  1100. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  1101. }
  1102. }
  1103. // hook up the video segment stream to the first track with h264 data
  1104. if (videoTrack && !pipeline.videoSegmentStream) {
  1105. pipeline.coalesceStream.numberOfTracks++;
  1106. pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack, options);
  1107. pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo) {
  1108. // When video emits timelineStartInfo data after a flush, we forward that
  1109. // info to the AudioSegmentStream, if it exists, because video timeline
  1110. // data takes precedence.
  1111. if (audioTrack) {
  1112. audioTrack.timelineStartInfo = timelineStartInfo;
  1113. // On the first segment we trim AAC frames that exist before the
  1114. // very earliest DTS we have seen in video because Chrome will
  1115. // interpret any video track with a baseMediaDecodeTime that is
  1116. // non-zero as a gap.
  1117. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  1118. }
  1119. });
  1120. pipeline.videoSegmentStream.on('processedGopsInfo',
  1121. self.trigger.bind(self, 'gopInfo'));
  1122. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function(baseMediaDecodeTime) {
  1123. if (audioTrack) {
  1124. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  1125. }
  1126. });
  1127. // Set up the final part of the video pipeline
  1128. pipeline.h264Stream
  1129. .pipe(pipeline.videoSegmentStream)
  1130. .pipe(pipeline.coalesceStream);
  1131. }
  1132. if (audioTrack && !pipeline.audioSegmentStream) {
  1133. // hook up the audio segment stream to the first track with aac data
  1134. pipeline.coalesceStream.numberOfTracks++;
  1135. pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack);
  1136. // Set up the final part of the audio pipeline
  1137. pipeline.adtsStream
  1138. .pipe(pipeline.audioSegmentStream)
  1139. .pipe(pipeline.coalesceStream);
  1140. }
  1141. }
  1142. });
  1143. // Re-emit any data coming from the coalesce stream to the outside world
  1144. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
  1145. // Let the consumer know we have finished flushing the entire pipeline
  1146. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  1147. };
  1148. // hook up the segment streams once track metadata is delivered
  1149. this.setBaseMediaDecodeTime = function(baseMediaDecodeTime) {
  1150. var pipeline = this.transmuxPipeline_;
  1151. this.baseMediaDecodeTime = baseMediaDecodeTime;
  1152. if (audioTrack) {
  1153. audioTrack.timelineStartInfo.dts = undefined;
  1154. audioTrack.timelineStartInfo.pts = undefined;
  1155. clearDtsInfo(audioTrack);
  1156. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  1157. if (pipeline.audioTimestampRolloverStream) {
  1158. pipeline.audioTimestampRolloverStream.discontinuity();
  1159. }
  1160. }
  1161. if (videoTrack) {
  1162. if (pipeline.videoSegmentStream) {
  1163. pipeline.videoSegmentStream.gopCache_ = [];
  1164. pipeline.videoTimestampRolloverStream.discontinuity();
  1165. }
  1166. videoTrack.timelineStartInfo.dts = undefined;
  1167. videoTrack.timelineStartInfo.pts = undefined;
  1168. clearDtsInfo(videoTrack);
  1169. pipeline.captionStream.reset();
  1170. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  1171. }
  1172. if (pipeline.timedMetadataTimestampRolloverStream) {
  1173. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  1174. }
  1175. };
  1176. this.setAudioAppendStart = function(timestamp) {
  1177. if (audioTrack) {
  1178. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  1179. }
  1180. };
  1181. this.alignGopsWith = function(gopsToAlignWith) {
  1182. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  1183. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  1184. }
  1185. };
  1186. // feed incoming data to the front of the parsing pipeline
  1187. this.push = function(data) {
  1188. if (hasFlushed) {
  1189. var isAac = isLikelyAacData(data);
  1190. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  1191. this.setupAacPipeline();
  1192. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  1193. this.setupTsPipeline();
  1194. }
  1195. hasFlushed = false;
  1196. }
  1197. this.transmuxPipeline_.headOfPipeline.push(data);
  1198. };
  1199. // flush any buffered data
  1200. this.flush = function() {
  1201. hasFlushed = true;
  1202. // Start at the top of the pipeline and flush all pending work
  1203. this.transmuxPipeline_.headOfPipeline.flush();
  1204. };
  1205. // Caption data has to be reset when seeking outside buffered range
  1206. this.resetCaptions = function() {
  1207. if (this.transmuxPipeline_.captionStream) {
  1208. this.transmuxPipeline_.captionStream.reset();
  1209. }
  1210. };
  1211. };
  1212. Transmuxer.prototype = new Stream();
  1213. module.exports = {
  1214. Transmuxer: Transmuxer,
  1215. VideoSegmentStream: VideoSegmentStream,
  1216. AudioSegmentStream: AudioSegmentStream,
  1217. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  1218. VIDEO_PROPERTIES: VIDEO_PROPERTIES
  1219. };