mp4-generator.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) Brightcove
  5. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  6. *
  7. * Functions that generate fragmented MP4s suitable for use with Media
  8. * Source Extensions.
  9. */
  10. 'use strict';
  11. var MAX_UINT32 = require('../utils/numbers.js').MAX_UINT32;
  12. var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
  13. (function () {
  14. var i;
  15. types = {
  16. avc1: [],
  17. // codingname
  18. avcC: [],
  19. btrt: [],
  20. dinf: [],
  21. dref: [],
  22. esds: [],
  23. ftyp: [],
  24. hdlr: [],
  25. mdat: [],
  26. mdhd: [],
  27. mdia: [],
  28. mfhd: [],
  29. minf: [],
  30. moof: [],
  31. moov: [],
  32. mp4a: [],
  33. // codingname
  34. mvex: [],
  35. mvhd: [],
  36. pasp: [],
  37. sdtp: [],
  38. smhd: [],
  39. stbl: [],
  40. stco: [],
  41. stsc: [],
  42. stsd: [],
  43. stsz: [],
  44. stts: [],
  45. styp: [],
  46. tfdt: [],
  47. tfhd: [],
  48. traf: [],
  49. trak: [],
  50. trun: [],
  51. trex: [],
  52. tkhd: [],
  53. vmhd: []
  54. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  55. // don't throw an error
  56. if (typeof Uint8Array === 'undefined') {
  57. return;
  58. }
  59. for (i in types) {
  60. if (types.hasOwnProperty(i)) {
  61. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  62. }
  63. }
  64. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  65. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  66. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  67. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  68. 0x00, 0x00, 0x00, // flags
  69. 0x00, 0x00, 0x00, 0x00, // pre_defined
  70. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  71. 0x00, 0x00, 0x00, 0x00, // reserved
  72. 0x00, 0x00, 0x00, 0x00, // reserved
  73. 0x00, 0x00, 0x00, 0x00, // reserved
  74. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  75. ]);
  76. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  77. 0x00, 0x00, 0x00, // flags
  78. 0x00, 0x00, 0x00, 0x00, // pre_defined
  79. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  80. 0x00, 0x00, 0x00, 0x00, // reserved
  81. 0x00, 0x00, 0x00, 0x00, // reserved
  82. 0x00, 0x00, 0x00, 0x00, // reserved
  83. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  84. ]);
  85. HDLR_TYPES = {
  86. video: VIDEO_HDLR,
  87. audio: AUDIO_HDLR
  88. };
  89. DREF = new Uint8Array([0x00, // version 0
  90. 0x00, 0x00, 0x00, // flags
  91. 0x00, 0x00, 0x00, 0x01, // entry_count
  92. 0x00, 0x00, 0x00, 0x0c, // entry_size
  93. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  94. 0x00, // version 0
  95. 0x00, 0x00, 0x01 // entry_flags
  96. ]);
  97. SMHD = new Uint8Array([0x00, // version
  98. 0x00, 0x00, 0x00, // flags
  99. 0x00, 0x00, // balance, 0 means centered
  100. 0x00, 0x00 // reserved
  101. ]);
  102. STCO = new Uint8Array([0x00, // version
  103. 0x00, 0x00, 0x00, // flags
  104. 0x00, 0x00, 0x00, 0x00 // entry_count
  105. ]);
  106. STSC = STCO;
  107. STSZ = new Uint8Array([0x00, // version
  108. 0x00, 0x00, 0x00, // flags
  109. 0x00, 0x00, 0x00, 0x00, // sample_size
  110. 0x00, 0x00, 0x00, 0x00 // sample_count
  111. ]);
  112. STTS = STCO;
  113. VMHD = new Uint8Array([0x00, // version
  114. 0x00, 0x00, 0x01, // flags
  115. 0x00, 0x00, // graphicsmode
  116. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  117. ]);
  118. })();
  119. box = function box(type) {
  120. var payload = [],
  121. size = 0,
  122. i,
  123. result,
  124. view;
  125. for (i = 1; i < arguments.length; i++) {
  126. payload.push(arguments[i]);
  127. }
  128. i = payload.length; // calculate the total size we need to allocate
  129. while (i--) {
  130. size += payload[i].byteLength;
  131. }
  132. result = new Uint8Array(size + 8);
  133. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  134. view.setUint32(0, result.byteLength);
  135. result.set(type, 4); // copy the payload into the result
  136. for (i = 0, size = 8; i < payload.length; i++) {
  137. result.set(payload[i], size);
  138. size += payload[i].byteLength;
  139. }
  140. return result;
  141. };
  142. dinf = function dinf() {
  143. return box(types.dinf, box(types.dref, DREF));
  144. };
  145. esds = function esds(track) {
  146. return box(types.esds, new Uint8Array([0x00, // version
  147. 0x00, 0x00, 0x00, // flags
  148. // ES_Descriptor
  149. 0x03, // tag, ES_DescrTag
  150. 0x19, // length
  151. 0x00, 0x00, // ES_ID
  152. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  153. // DecoderConfigDescriptor
  154. 0x04, // tag, DecoderConfigDescrTag
  155. 0x11, // length
  156. 0x40, // object type
  157. 0x15, // streamType
  158. 0x00, 0x06, 0x00, // bufferSizeDB
  159. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  160. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  161. // DecoderSpecificInfo
  162. 0x05, // tag, DecoderSpecificInfoTag
  163. 0x02, // length
  164. // ISO/IEC 14496-3, AudioSpecificConfig
  165. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  166. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  167. ]));
  168. };
  169. ftyp = function ftyp() {
  170. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  171. };
  172. hdlr = function hdlr(type) {
  173. return box(types.hdlr, HDLR_TYPES[type]);
  174. };
  175. mdat = function mdat(data) {
  176. return box(types.mdat, data);
  177. };
  178. mdhd = function mdhd(track) {
  179. var result = new Uint8Array([0x00, // version 0
  180. 0x00, 0x00, 0x00, // flags
  181. 0x00, 0x00, 0x00, 0x02, // creation_time
  182. 0x00, 0x00, 0x00, 0x03, // modification_time
  183. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  184. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  185. 0x55, 0xc4, // 'und' language (undetermined)
  186. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  187. // defined. The sample rate can be parsed out of an ADTS header, for
  188. // instance.
  189. if (track.samplerate) {
  190. result[12] = track.samplerate >>> 24 & 0xFF;
  191. result[13] = track.samplerate >>> 16 & 0xFF;
  192. result[14] = track.samplerate >>> 8 & 0xFF;
  193. result[15] = track.samplerate & 0xFF;
  194. }
  195. return box(types.mdhd, result);
  196. };
  197. mdia = function mdia(track) {
  198. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  199. };
  200. mfhd = function mfhd(sequenceNumber) {
  201. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  202. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  203. ]));
  204. };
  205. minf = function minf(track) {
  206. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  207. };
  208. moof = function moof(sequenceNumber, tracks) {
  209. var trackFragments = [],
  210. i = tracks.length; // build traf boxes for each track fragment
  211. while (i--) {
  212. trackFragments[i] = traf(tracks[i]);
  213. }
  214. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  215. };
  216. /**
  217. * Returns a movie box.
  218. * @param tracks {array} the tracks associated with this movie
  219. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  220. */
  221. moov = function moov(tracks) {
  222. var i = tracks.length,
  223. boxes = [];
  224. while (i--) {
  225. boxes[i] = trak(tracks[i]);
  226. }
  227. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  228. };
  229. mvex = function mvex(tracks) {
  230. var i = tracks.length,
  231. boxes = [];
  232. while (i--) {
  233. boxes[i] = trex(tracks[i]);
  234. }
  235. return box.apply(null, [types.mvex].concat(boxes));
  236. };
  237. mvhd = function mvhd(duration) {
  238. var bytes = new Uint8Array([0x00, // version 0
  239. 0x00, 0x00, 0x00, // flags
  240. 0x00, 0x00, 0x00, 0x01, // creation_time
  241. 0x00, 0x00, 0x00, 0x02, // modification_time
  242. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  243. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  244. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  245. 0x01, 0x00, // 1.0 volume
  246. 0x00, 0x00, // reserved
  247. 0x00, 0x00, 0x00, 0x00, // reserved
  248. 0x00, 0x00, 0x00, 0x00, // reserved
  249. 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  250. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  251. 0xff, 0xff, 0xff, 0xff // next_track_ID
  252. ]);
  253. return box(types.mvhd, bytes);
  254. };
  255. sdtp = function sdtp(track) {
  256. var samples = track.samples || [],
  257. bytes = new Uint8Array(4 + samples.length),
  258. flags,
  259. i; // leave the full box header (4 bytes) all zero
  260. // write the sample table
  261. for (i = 0; i < samples.length; i++) {
  262. flags = samples[i].flags;
  263. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  264. }
  265. return box(types.sdtp, bytes);
  266. };
  267. stbl = function stbl(track) {
  268. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  269. };
  270. (function () {
  271. var videoSample, audioSample;
  272. stsd = function stsd(track) {
  273. return box(types.stsd, new Uint8Array([0x00, // version 0
  274. 0x00, 0x00, 0x00, // flags
  275. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  276. };
  277. videoSample = function videoSample(track) {
  278. var sps = track.sps || [],
  279. pps = track.pps || [],
  280. sequenceParameterSets = [],
  281. pictureParameterSets = [],
  282. i,
  283. avc1Box; // assemble the SPSs
  284. for (i = 0; i < sps.length; i++) {
  285. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  286. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  287. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  288. } // assemble the PPSs
  289. for (i = 0; i < pps.length; i++) {
  290. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  291. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  292. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  293. }
  294. avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  295. 0x00, 0x01, // data_reference_index
  296. 0x00, 0x00, // pre_defined
  297. 0x00, 0x00, // reserved
  298. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  299. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  300. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  301. 0x00, 0x48, 0x00, 0x00, // horizresolution
  302. 0x00, 0x48, 0x00, 0x00, // vertresolution
  303. 0x00, 0x00, 0x00, 0x00, // reserved
  304. 0x00, 0x01, // frame_count
  305. 0x13, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6a, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2d, 0x68, 0x6c, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname
  306. 0x00, 0x18, // depth = 24
  307. 0x11, 0x11 // pre_defined = -1
  308. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  309. track.profileIdc, // AVCProfileIndication
  310. track.profileCompatibility, // profile_compatibility
  311. track.levelIdc, // AVCLevelIndication
  312. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  313. ].concat([sps.length], // numOfSequenceParameterSets
  314. sequenceParameterSets, // "SPS"
  315. [pps.length], // numOfPictureParameterSets
  316. pictureParameterSets // "PPS"
  317. ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  318. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  319. 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
  320. ]))];
  321. if (track.sarRatio) {
  322. var hSpacing = track.sarRatio[0],
  323. vSpacing = track.sarRatio[1];
  324. avc1Box.push(box(types.pasp, new Uint8Array([(hSpacing & 0xFF000000) >> 24, (hSpacing & 0xFF0000) >> 16, (hSpacing & 0xFF00) >> 8, hSpacing & 0xFF, (vSpacing & 0xFF000000) >> 24, (vSpacing & 0xFF0000) >> 16, (vSpacing & 0xFF00) >> 8, vSpacing & 0xFF])));
  325. }
  326. return box.apply(null, avc1Box);
  327. };
  328. audioSample = function audioSample(track) {
  329. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  330. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  331. 0x00, 0x01, // data_reference_index
  332. // AudioSampleEntry, ISO/IEC 14496-12
  333. 0x00, 0x00, 0x00, 0x00, // reserved
  334. 0x00, 0x00, 0x00, 0x00, // reserved
  335. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  336. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  337. 0x00, 0x00, // pre_defined
  338. 0x00, 0x00, // reserved
  339. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  340. // MP4AudioSampleEntry, ISO/IEC 14496-14
  341. ]), esds(track));
  342. };
  343. })();
  344. tkhd = function tkhd(track) {
  345. var result = new Uint8Array([0x00, // version 0
  346. 0x00, 0x00, 0x07, // flags
  347. 0x00, 0x00, 0x00, 0x00, // creation_time
  348. 0x00, 0x00, 0x00, 0x00, // modification_time
  349. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  350. 0x00, 0x00, 0x00, 0x00, // reserved
  351. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  352. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  353. 0x00, 0x00, // layer
  354. 0x00, 0x00, // alternate_group
  355. 0x01, 0x00, // non-audio track volume
  356. 0x00, 0x00, // reserved
  357. 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  358. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  359. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  360. ]);
  361. return box(types.tkhd, result);
  362. };
  363. /**
  364. * Generate a track fragment (traf) box. A traf box collects metadata
  365. * about tracks in a movie fragment (moof) box.
  366. */
  367. traf = function traf(track) {
  368. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  369. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  370. 0x00, 0x00, 0x3a, // flags
  371. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  372. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  373. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  374. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  375. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  376. ]));
  377. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / MAX_UINT32);
  378. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % MAX_UINT32);
  379. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  380. 0x00, 0x00, 0x00, // flags
  381. // baseMediaDecodeTime
  382. upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of
  383. // the containing moof to the first payload byte of the associated
  384. // mdat
  385. dataOffset = 32 + // tfhd
  386. 20 + // tfdt
  387. 8 + // traf header
  388. 16 + // mfhd
  389. 8 + // moof header
  390. 8; // mdat header
  391. // audio tracks require less metadata
  392. if (track.type === 'audio') {
  393. trackFragmentRun = trun(track, dataOffset);
  394. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  395. } // video tracks should contain an independent and disposable samples
  396. // box (sdtp)
  397. // generate one and adjust offsets to match
  398. sampleDependencyTable = sdtp(track);
  399. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  400. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  401. };
  402. /**
  403. * Generate a track box.
  404. * @param track {object} a track definition
  405. * @return {Uint8Array} the track box
  406. */
  407. trak = function trak(track) {
  408. track.duration = track.duration || 0xffffffff;
  409. return box(types.trak, tkhd(track), mdia(track));
  410. };
  411. trex = function trex(track) {
  412. var result = new Uint8Array([0x00, // version 0
  413. 0x00, 0x00, 0x00, // flags
  414. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  415. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  416. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  417. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  418. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  419. ]); // the last two bytes of default_sample_flags is the sample
  420. // degradation priority, a hint about the importance of this sample
  421. // relative to others. Lower the degradation priority for all sample
  422. // types other than video.
  423. if (track.type !== 'video') {
  424. result[result.length - 1] = 0x00;
  425. }
  426. return box(types.trex, result);
  427. };
  428. (function () {
  429. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  430. // duration is present for the first sample, it will be present for
  431. // all subsequent samples.
  432. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  433. trunHeader = function trunHeader(samples, offset) {
  434. var durationPresent = 0,
  435. sizePresent = 0,
  436. flagsPresent = 0,
  437. compositionTimeOffset = 0; // trun flag constants
  438. if (samples.length) {
  439. if (samples[0].duration !== undefined) {
  440. durationPresent = 0x1;
  441. }
  442. if (samples[0].size !== undefined) {
  443. sizePresent = 0x2;
  444. }
  445. if (samples[0].flags !== undefined) {
  446. flagsPresent = 0x4;
  447. }
  448. if (samples[0].compositionTimeOffset !== undefined) {
  449. compositionTimeOffset = 0x8;
  450. }
  451. }
  452. return [0x00, // version 0
  453. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  454. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  455. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  456. ];
  457. };
  458. videoTrun = function videoTrun(track, offset) {
  459. var bytesOffest, bytes, header, samples, sample, i;
  460. samples = track.samples || [];
  461. offset += 8 + 12 + 16 * samples.length;
  462. header = trunHeader(samples, offset);
  463. bytes = new Uint8Array(header.length + samples.length * 16);
  464. bytes.set(header);
  465. bytesOffest = header.length;
  466. for (i = 0; i < samples.length; i++) {
  467. sample = samples[i];
  468. bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
  469. bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
  470. bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
  471. bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
  472. bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
  473. bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
  474. bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
  475. bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
  476. bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
  477. bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
  478. bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
  479. bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
  480. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
  481. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
  482. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
  483. bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
  484. }
  485. return box(types.trun, bytes);
  486. };
  487. audioTrun = function audioTrun(track, offset) {
  488. var bytes, bytesOffest, header, samples, sample, i;
  489. samples = track.samples || [];
  490. offset += 8 + 12 + 8 * samples.length;
  491. header = trunHeader(samples, offset);
  492. bytes = new Uint8Array(header.length + samples.length * 8);
  493. bytes.set(header);
  494. bytesOffest = header.length;
  495. for (i = 0; i < samples.length; i++) {
  496. sample = samples[i];
  497. bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
  498. bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
  499. bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
  500. bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
  501. bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
  502. bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
  503. bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
  504. bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
  505. }
  506. return box(types.trun, bytes);
  507. };
  508. trun = function trun(track, offset) {
  509. if (track.type === 'audio') {
  510. return audioTrun(track, offset);
  511. }
  512. return videoTrun(track, offset);
  513. };
  514. })();
  515. module.exports = {
  516. ftyp: ftyp,
  517. mdat: mdat,
  518. moof: moof,
  519. moov: moov,
  520. initSegment: function initSegment(tracks) {
  521. var fileType = ftyp(),
  522. movie = moov(tracks),
  523. result;
  524. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  525. result.set(fileType);
  526. result.set(movie, fileType.byteLength);
  527. return result;
  528. }
  529. };