Home Reference Source

src/remux/mp4-generator.js

  1. /**
  2. * Generate MP4 Box
  3. */
  4.  
  5. const UINT32_MAX = Math.pow(2, 32) - 1;
  6.  
  7. class MP4 {
  8. static init () {
  9. MP4.types = {
  10. avc1: [], // codingname
  11. avcC: [],
  12. btrt: [],
  13. dinf: [],
  14. dref: [],
  15. esds: [],
  16. ftyp: [],
  17. hdlr: [],
  18. mdat: [],
  19. mdhd: [],
  20. mdia: [],
  21. mfhd: [],
  22. minf: [],
  23. moof: [],
  24. moov: [],
  25. mp4a: [],
  26. '.mp3': [],
  27. mvex: [],
  28. mvhd: [],
  29. pasp: [],
  30. sdtp: [],
  31. stbl: [],
  32. stco: [],
  33. stsc: [],
  34. stsd: [],
  35. stsz: [],
  36. stts: [],
  37. tfdt: [],
  38. tfhd: [],
  39. traf: [],
  40. trak: [],
  41. trun: [],
  42. trex: [],
  43. tkhd: [],
  44. vmhd: [],
  45. smhd: []
  46. };
  47.  
  48. let i;
  49. for (i in MP4.types) {
  50. if (MP4.types.hasOwnProperty(i)) {
  51. MP4.types[i] = [
  52. i.charCodeAt(0),
  53. i.charCodeAt(1),
  54. i.charCodeAt(2),
  55. i.charCodeAt(3)
  56. ];
  57. }
  58. }
  59.  
  60. let videoHdlr = new Uint8Array([
  61. 0x00, // version 0
  62. 0x00, 0x00, 0x00, // flags
  63. 0x00, 0x00, 0x00, 0x00, // pre_defined
  64. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  65. 0x00, 0x00, 0x00, 0x00, // reserved
  66. 0x00, 0x00, 0x00, 0x00, // reserved
  67. 0x00, 0x00, 0x00, 0x00, // reserved
  68. 0x56, 0x69, 0x64, 0x65,
  69. 0x6f, 0x48, 0x61, 0x6e,
  70. 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  71. ]);
  72.  
  73. let audioHdlr = new Uint8Array([
  74. 0x00, // version 0
  75. 0x00, 0x00, 0x00, // flags
  76. 0x00, 0x00, 0x00, 0x00, // pre_defined
  77. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  78. 0x00, 0x00, 0x00, 0x00, // reserved
  79. 0x00, 0x00, 0x00, 0x00, // reserved
  80. 0x00, 0x00, 0x00, 0x00, // reserved
  81. 0x53, 0x6f, 0x75, 0x6e,
  82. 0x64, 0x48, 0x61, 0x6e,
  83. 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  84. ]);
  85.  
  86. MP4.HDLR_TYPES = {
  87. 'video': videoHdlr,
  88. 'audio': audioHdlr
  89. };
  90.  
  91. let dref = new Uint8Array([
  92. 0x00, // version 0
  93. 0x00, 0x00, 0x00, // flags
  94. 0x00, 0x00, 0x00, 0x01, // entry_count
  95. 0x00, 0x00, 0x00, 0x0c, // entry_size
  96. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  97. 0x00, // version 0
  98. 0x00, 0x00, 0x01 // entry_flags
  99. ]);
  100.  
  101. let stco = new Uint8Array([
  102. 0x00, // version
  103. 0x00, 0x00, 0x00, // flags
  104. 0x00, 0x00, 0x00, 0x00 // entry_count
  105. ]);
  106.  
  107. MP4.STTS = MP4.STSC = MP4.STCO = stco;
  108.  
  109. MP4.STSZ = new Uint8Array([
  110. 0x00, // version
  111. 0x00, 0x00, 0x00, // flags
  112. 0x00, 0x00, 0x00, 0x00, // sample_size
  113. 0x00, 0x00, 0x00, 0x00 // sample_count
  114. ]);
  115. MP4.VMHD = new Uint8Array([
  116. 0x00, // version
  117. 0x00, 0x00, 0x01, // flags
  118. 0x00, 0x00, // graphicsmode
  119. 0x00, 0x00,
  120. 0x00, 0x00,
  121. 0x00, 0x00 // opcolor
  122. ]);
  123. MP4.SMHD = new Uint8Array([
  124. 0x00, // version
  125. 0x00, 0x00, 0x00, // flags
  126. 0x00, 0x00, // balance
  127. 0x00, 0x00 // reserved
  128. ]);
  129.  
  130. MP4.STSD = new Uint8Array([
  131. 0x00, // version 0
  132. 0x00, 0x00, 0x00, // flags
  133. 0x00, 0x00, 0x00, 0x01]);// entry_count
  134.  
  135. let majorBrand = new Uint8Array([105, 115, 111, 109]); // isom
  136. let avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1
  137. let minorVersion = new Uint8Array([0, 0, 0, 1]);
  138.  
  139. MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand);
  140. MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref));
  141. }
  142.  
  143. static box (type) {
  144. let
  145. payload = Array.prototype.slice.call(arguments, 1),
  146. size = 8,
  147. i = payload.length,
  148. len = i,
  149. result;
  150. // calculate the total size we need to allocate
  151. while (i--) {
  152. size += payload[i].byteLength;
  153. }
  154.  
  155. result = new Uint8Array(size);
  156. result[0] = (size >> 24) & 0xff;
  157. result[1] = (size >> 16) & 0xff;
  158. result[2] = (size >> 8) & 0xff;
  159. result[3] = size & 0xff;
  160. result.set(type, 4);
  161. // copy the payload into the result
  162. for (i = 0, size = 8; i < len; i++) {
  163. // copy payload[i] array @ offset size
  164. result.set(payload[i], size);
  165. size += payload[i].byteLength;
  166. }
  167. return result;
  168. }
  169.  
  170. static hdlr (type) {
  171. return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]);
  172. }
  173.  
  174. static mdat (data) {
  175. return MP4.box(MP4.types.mdat, data);
  176. }
  177.  
  178. static mdhd (timescale, duration) {
  179. duration *= timescale;
  180. const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
  181. const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
  182. return MP4.box(MP4.types.mdhd, new Uint8Array([
  183. 0x01, // version 1
  184. 0x00, 0x00, 0x00, // flags
  185. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time
  186. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time
  187. (timescale >> 24) & 0xFF,
  188. (timescale >> 16) & 0xFF,
  189. (timescale >> 8) & 0xFF,
  190. timescale & 0xFF, // timescale
  191. (upperWordDuration >> 24),
  192. (upperWordDuration >> 16) & 0xFF,
  193. (upperWordDuration >> 8) & 0xFF,
  194. upperWordDuration & 0xFF,
  195. (lowerWordDuration >> 24),
  196. (lowerWordDuration >> 16) & 0xFF,
  197. (lowerWordDuration >> 8) & 0xFF,
  198. lowerWordDuration & 0xFF,
  199. 0x55, 0xc4, // 'und' language (undetermined)
  200. 0x00, 0x00
  201. ]));
  202. }
  203.  
  204. static mdia (track) {
  205. return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track));
  206. }
  207.  
  208. static mfhd (sequenceNumber) {
  209. return MP4.box(MP4.types.mfhd, new Uint8Array([
  210. 0x00,
  211. 0x00, 0x00, 0x00, // flags
  212. (sequenceNumber >> 24),
  213. (sequenceNumber >> 16) & 0xFF,
  214. (sequenceNumber >> 8) & 0xFF,
  215. sequenceNumber & 0xFF // sequence_number
  216. ]));
  217. }
  218.  
  219. static minf (track) {
  220. if (track.type === 'audio') {
  221. return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track));
  222. } else {
  223. return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track));
  224. }
  225. }
  226.  
  227. static moof (sn, baseMediaDecodeTime, track) {
  228. return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime));
  229. }
  230. /**
  231. * @param tracks... (optional) {array} the tracks associated with this movie
  232. */
  233. static moov (tracks) {
  234. let
  235. i = tracks.length,
  236. boxes = [];
  237.  
  238. while (i--) {
  239. boxes[i] = MP4.trak(tracks[i]);
  240. }
  241.  
  242. return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(tracks[0].timescale, tracks[0].duration)].concat(boxes).concat(MP4.mvex(tracks)));
  243. }
  244.  
  245. static mvex (tracks) {
  246. let
  247. i = tracks.length,
  248. boxes = [];
  249.  
  250. while (i--) {
  251. boxes[i] = MP4.trex(tracks[i]);
  252. }
  253.  
  254. return MP4.box.apply(null, [MP4.types.mvex].concat(boxes));
  255. }
  256.  
  257. static mvhd (timescale, duration) {
  258. duration *= timescale;
  259. const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
  260. const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
  261. let
  262. bytes = new Uint8Array([
  263. 0x01, // version 1
  264. 0x00, 0x00, 0x00, // flags
  265. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time
  266. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time
  267. (timescale >> 24) & 0xFF,
  268. (timescale >> 16) & 0xFF,
  269. (timescale >> 8) & 0xFF,
  270. timescale & 0xFF, // timescale
  271. (upperWordDuration >> 24),
  272. (upperWordDuration >> 16) & 0xFF,
  273. (upperWordDuration >> 8) & 0xFF,
  274. upperWordDuration & 0xFF,
  275. (lowerWordDuration >> 24),
  276. (lowerWordDuration >> 16) & 0xFF,
  277. (lowerWordDuration >> 8) & 0xFF,
  278. lowerWordDuration & 0xFF,
  279. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  280. 0x01, 0x00, // 1.0 volume
  281. 0x00, 0x00, // reserved
  282. 0x00, 0x00, 0x00, 0x00, // reserved
  283. 0x00, 0x00, 0x00, 0x00, // reserved
  284. 0x00, 0x01, 0x00, 0x00,
  285. 0x00, 0x00, 0x00, 0x00,
  286. 0x00, 0x00, 0x00, 0x00,
  287. 0x00, 0x00, 0x00, 0x00,
  288. 0x00, 0x01, 0x00, 0x00,
  289. 0x00, 0x00, 0x00, 0x00,
  290. 0x00, 0x00, 0x00, 0x00,
  291. 0x00, 0x00, 0x00, 0x00,
  292. 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  293. 0x00, 0x00, 0x00, 0x00,
  294. 0x00, 0x00, 0x00, 0x00,
  295. 0x00, 0x00, 0x00, 0x00,
  296. 0x00, 0x00, 0x00, 0x00,
  297. 0x00, 0x00, 0x00, 0x00,
  298. 0x00, 0x00, 0x00, 0x00, // pre_defined
  299. 0xff, 0xff, 0xff, 0xff // next_track_ID
  300. ]);
  301. return MP4.box(MP4.types.mvhd, bytes);
  302. }
  303.  
  304. static sdtp (track) {
  305. let
  306. samples = track.samples || [],
  307. bytes = new Uint8Array(4 + samples.length),
  308. flags,
  309. i;
  310. // leave the full box header (4 bytes) all zero
  311. // write the sample table
  312. for (i = 0; i < samples.length; i++) {
  313. flags = samples[i].flags;
  314. bytes[i + 4] = (flags.dependsOn << 4) |
  315. (flags.isDependedOn << 2) |
  316. (flags.hasRedundancy);
  317. }
  318.  
  319. return MP4.box(MP4.types.sdtp, bytes);
  320. }
  321.  
  322. static stbl (track) {
  323. return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO));
  324. }
  325.  
  326. static avc1 (track) {
  327. let sps = [], pps = [], i, data, len;
  328. // assemble the SPSs
  329.  
  330. for (i = 0; i < track.sps.length; i++) {
  331. data = track.sps[i];
  332. len = data.byteLength;
  333. sps.push((len >>> 8) & 0xFF);
  334. sps.push((len & 0xFF));
  335.  
  336. // SPS
  337. sps = sps.concat(Array.prototype.slice.call(data));
  338. }
  339.  
  340. // assemble the PPSs
  341. for (i = 0; i < track.pps.length; i++) {
  342. data = track.pps[i];
  343. len = data.byteLength;
  344. pps.push((len >>> 8) & 0xFF);
  345. pps.push((len & 0xFF));
  346.  
  347. pps = pps.concat(Array.prototype.slice.call(data));
  348. }
  349.  
  350. let avcc = MP4.box(MP4.types.avcC, new Uint8Array([
  351. 0x01, // version
  352. sps[3], // profile
  353. sps[4], // profile compat
  354. sps[5], // level
  355. 0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes
  356. 0xE0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets
  357. ].concat(sps).concat([
  358. track.pps.length // numOfPictureParameterSets
  359. ]).concat(pps))), // "PPS"
  360. width = track.width,
  361. height = track.height,
  362. hSpacing = track.pixelRatio[0],
  363. vSpacing = track.pixelRatio[1];
  364.  
  365. return MP4.box(MP4.types.avc1, new Uint8Array([
  366. 0x00, 0x00, 0x00, // reserved
  367. 0x00, 0x00, 0x00, // reserved
  368. 0x00, 0x01, // data_reference_index
  369. 0x00, 0x00, // pre_defined
  370. 0x00, 0x00, // reserved
  371. 0x00, 0x00, 0x00, 0x00,
  372. 0x00, 0x00, 0x00, 0x00,
  373. 0x00, 0x00, 0x00, 0x00, // pre_defined
  374. (width >> 8) & 0xFF,
  375. width & 0xff, // width
  376. (height >> 8) & 0xFF,
  377. height & 0xff, // height
  378. 0x00, 0x48, 0x00, 0x00, // horizresolution
  379. 0x00, 0x48, 0x00, 0x00, // vertresolution
  380. 0x00, 0x00, 0x00, 0x00, // reserved
  381. 0x00, 0x01, // frame_count
  382. 0x12,
  383. 0x64, 0x61, 0x69, 0x6C, // dailymotion/hls.js
  384. 0x79, 0x6D, 0x6F, 0x74,
  385. 0x69, 0x6F, 0x6E, 0x2F,
  386. 0x68, 0x6C, 0x73, 0x2E,
  387. 0x6A, 0x73, 0x00, 0x00,
  388. 0x00, 0x00, 0x00, 0x00,
  389. 0x00, 0x00, 0x00, 0x00,
  390. 0x00, 0x00, 0x00, // compressorname
  391. 0x00, 0x18, // depth = 24
  392. 0x11, 0x11]), // pre_defined = -1
  393. avcc,
  394. MP4.box(MP4.types.btrt, new Uint8Array([
  395. 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  396. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  397. 0x00, 0x2d, 0xc6, 0xc0])), // avgBitrate
  398. MP4.box(MP4.types.pasp, new Uint8Array([
  399. (hSpacing >> 24), // hSpacing
  400. (hSpacing >> 16) & 0xFF,
  401. (hSpacing >> 8) & 0xFF,
  402. hSpacing & 0xFF,
  403. (vSpacing >> 24), // vSpacing
  404. (vSpacing >> 16) & 0xFF,
  405. (vSpacing >> 8) & 0xFF,
  406. vSpacing & 0xFF]))
  407. );
  408. }
  409.  
  410. static esds (track) {
  411. let configlen = track.config.length;
  412. return new Uint8Array([
  413. 0x00, // version 0
  414. 0x00, 0x00, 0x00, // flags
  415.  
  416. 0x03, // descriptor_type
  417. 0x17 + configlen, // length
  418. 0x00, 0x01, // es_id
  419. 0x00, // stream_priority
  420.  
  421. 0x04, // descriptor_type
  422. 0x0f + configlen, // length
  423. 0x40, // codec : mpeg4_audio
  424. 0x15, // stream_type
  425. 0x00, 0x00, 0x00, // buffer_size
  426. 0x00, 0x00, 0x00, 0x00, // maxBitrate
  427. 0x00, 0x00, 0x00, 0x00, // avgBitrate
  428.  
  429. 0x05 // descriptor_type
  430. ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor
  431. }
  432.  
  433. static mp4a (track) {
  434. let samplerate = track.samplerate;
  435. return MP4.box(MP4.types.mp4a, new Uint8Array([
  436. 0x00, 0x00, 0x00, // reserved
  437. 0x00, 0x00, 0x00, // reserved
  438. 0x00, 0x01, // data_reference_index
  439. 0x00, 0x00, 0x00, 0x00,
  440. 0x00, 0x00, 0x00, 0x00, // reserved
  441. 0x00, track.channelCount, // channelcount
  442. 0x00, 0x10, // sampleSize:16bits
  443. 0x00, 0x00, 0x00, 0x00, // reserved2
  444. (samplerate >> 8) & 0xFF,
  445. samplerate & 0xff, //
  446. 0x00, 0x00]),
  447. MP4.box(MP4.types.esds, MP4.esds(track)));
  448. }
  449.  
  450. static mp3 (track) {
  451. let samplerate = track.samplerate;
  452. return MP4.box(MP4.types['.mp3'], new Uint8Array([
  453. 0x00, 0x00, 0x00, // reserved
  454. 0x00, 0x00, 0x00, // reserved
  455. 0x00, 0x01, // data_reference_index
  456. 0x00, 0x00, 0x00, 0x00,
  457. 0x00, 0x00, 0x00, 0x00, // reserved
  458. 0x00, track.channelCount, // channelcount
  459. 0x00, 0x10, // sampleSize:16bits
  460. 0x00, 0x00, 0x00, 0x00, // reserved2
  461. (samplerate >> 8) & 0xFF,
  462. samplerate & 0xff, //
  463. 0x00, 0x00]));
  464. }
  465.  
  466. static stsd (track) {
  467. if (track.type === 'audio') {
  468. if (!track.isAAC && track.codec === 'mp3') {
  469. return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track));
  470. }
  471.  
  472. return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
  473. } else {
  474. return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
  475. }
  476. }
  477.  
  478. static tkhd (track) {
  479. let id = track.id,
  480. duration = track.duration * track.timescale,
  481. width = track.width,
  482. height = track.height,
  483. upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)),
  484. lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
  485. return MP4.box(MP4.types.tkhd, new Uint8Array([
  486. 0x01, // version 1
  487. 0x00, 0x00, 0x07, // flags
  488. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time
  489. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time
  490. (id >> 24) & 0xFF,
  491. (id >> 16) & 0xFF,
  492. (id >> 8) & 0xFF,
  493. id & 0xFF, // track_ID
  494. 0x00, 0x00, 0x00, 0x00, // reserved
  495. (upperWordDuration >> 24),
  496. (upperWordDuration >> 16) & 0xFF,
  497. (upperWordDuration >> 8) & 0xFF,
  498. upperWordDuration & 0xFF,
  499. (lowerWordDuration >> 24),
  500. (lowerWordDuration >> 16) & 0xFF,
  501. (lowerWordDuration >> 8) & 0xFF,
  502. lowerWordDuration & 0xFF,
  503. 0x00, 0x00, 0x00, 0x00,
  504. 0x00, 0x00, 0x00, 0x00, // reserved
  505. 0x00, 0x00, // layer
  506. 0x00, 0x00, // alternate_group
  507. 0x00, 0x00, // non-audio track volume
  508. 0x00, 0x00, // reserved
  509. 0x00, 0x01, 0x00, 0x00,
  510. 0x00, 0x00, 0x00, 0x00,
  511. 0x00, 0x00, 0x00, 0x00,
  512. 0x00, 0x00, 0x00, 0x00,
  513. 0x00, 0x01, 0x00, 0x00,
  514. 0x00, 0x00, 0x00, 0x00,
  515. 0x00, 0x00, 0x00, 0x00,
  516. 0x00, 0x00, 0x00, 0x00,
  517. 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  518. (width >> 8) & 0xFF,
  519. width & 0xFF,
  520. 0x00, 0x00, // width
  521. (height >> 8) & 0xFF,
  522. height & 0xFF,
  523. 0x00, 0x00 // height
  524. ]));
  525. }
  526.  
  527. static traf (track, baseMediaDecodeTime) {
  528. let sampleDependencyTable = MP4.sdtp(track),
  529. id = track.id,
  530. upperWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1)),
  531. lowerWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1));
  532. return MP4.box(MP4.types.traf,
  533. MP4.box(MP4.types.tfhd, new Uint8Array([
  534. 0x00, // version 0
  535. 0x00, 0x00, 0x00, // flags
  536. (id >> 24),
  537. (id >> 16) & 0XFF,
  538. (id >> 8) & 0XFF,
  539. (id & 0xFF) // track_ID
  540. ])),
  541. MP4.box(MP4.types.tfdt, new Uint8Array([
  542. 0x01, // version 1
  543. 0x00, 0x00, 0x00, // flags
  544. (upperWordBaseMediaDecodeTime >> 24),
  545. (upperWordBaseMediaDecodeTime >> 16) & 0XFF,
  546. (upperWordBaseMediaDecodeTime >> 8) & 0XFF,
  547. (upperWordBaseMediaDecodeTime & 0xFF),
  548. (lowerWordBaseMediaDecodeTime >> 24),
  549. (lowerWordBaseMediaDecodeTime >> 16) & 0XFF,
  550. (lowerWordBaseMediaDecodeTime >> 8) & 0XFF,
  551. (lowerWordBaseMediaDecodeTime & 0xFF)
  552. ])),
  553. MP4.trun(track,
  554. sampleDependencyTable.length +
  555. 16 + // tfhd
  556. 20 + // tfdt
  557. 8 + // traf header
  558. 16 + // mfhd
  559. 8 + // moof header
  560. 8), // mdat header
  561. sampleDependencyTable);
  562. }
  563.  
  564. /**
  565. * Generate a track box.
  566. * @param track {object} a track definition
  567. * @return {Uint8Array} the track box
  568. */
  569. static trak (track) {
  570. track.duration = track.duration || 0xffffffff;
  571. return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));
  572. }
  573.  
  574. static trex (track) {
  575. let id = track.id;
  576. return MP4.box(MP4.types.trex, new Uint8Array([
  577. 0x00, // version 0
  578. 0x00, 0x00, 0x00, // flags
  579. (id >> 24),
  580. (id >> 16) & 0XFF,
  581. (id >> 8) & 0XFF,
  582. (id & 0xFF), // track_ID
  583. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  584. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  585. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  586. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  587. ]));
  588. }
  589.  
  590. static trun (track, offset) {
  591. let samples = track.samples || [],
  592. len = samples.length,
  593. arraylen = 12 + (16 * len),
  594. array = new Uint8Array(arraylen),
  595. i, sample, duration, size, flags, cts;
  596. offset += 8 + arraylen;
  597. array.set([
  598. 0x00, // version 0
  599. 0x00, 0x0f, 0x01, // flags
  600. (len >>> 24) & 0xFF,
  601. (len >>> 16) & 0xFF,
  602. (len >>> 8) & 0xFF,
  603. len & 0xFF, // sample_count
  604. (offset >>> 24) & 0xFF,
  605. (offset >>> 16) & 0xFF,
  606. (offset >>> 8) & 0xFF,
  607. offset & 0xFF // data_offset
  608. ], 0);
  609. for (i = 0; i < len; i++) {
  610. sample = samples[i];
  611. duration = sample.duration;
  612. size = sample.size;
  613. flags = sample.flags;
  614. cts = sample.cts;
  615. array.set([
  616. (duration >>> 24) & 0xFF,
  617. (duration >>> 16) & 0xFF,
  618. (duration >>> 8) & 0xFF,
  619. duration & 0xFF, // sample_duration
  620. (size >>> 24) & 0xFF,
  621. (size >>> 16) & 0xFF,
  622. (size >>> 8) & 0xFF,
  623. size & 0xFF, // sample_size
  624. (flags.isLeading << 2) | flags.dependsOn,
  625. (flags.isDependedOn << 6) |
  626. (flags.hasRedundancy << 4) |
  627. (flags.paddingValue << 1) |
  628. flags.isNonSync,
  629. flags.degradPrio & 0xF0 << 8,
  630. flags.degradPrio & 0x0F, // sample_flags
  631. (cts >>> 24) & 0xFF,
  632. (cts >>> 16) & 0xFF,
  633. (cts >>> 8) & 0xFF,
  634. cts & 0xFF // sample_composition_time_offset
  635. ], 12 + 16 * i);
  636. }
  637. return MP4.box(MP4.types.trun, array);
  638. }
  639.  
  640. static initSegment (tracks) {
  641. if (!MP4.types) {
  642. MP4.init();
  643. }
  644.  
  645. let movie = MP4.moov(tracks), result;
  646. result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength);
  647. result.set(MP4.FTYP);
  648. result.set(movie, MP4.FTYP.byteLength);
  649. return result;
  650. }
  651. }
  652.  
  653. export default MP4;