Buckets:
| /** | |
| * Generate MP4 Box | |
| */ | |
| import { appendUint8Array } from '../utils/mp4-tools'; | |
| import type { | |
| DemuxedAC3, | |
| DemuxedAudioTrack, | |
| DemuxedAVC1, | |
| DemuxedHEVC, | |
| DemuxedVideoTrack, | |
| } from '../types/demuxer'; | |
| import type { | |
| Mp4SampleFlags, | |
| RemuxedAudioTrackSamples, | |
| RemuxedVideoTrackSamples, | |
| } from '../types/remuxer'; | |
| type MediaTrackType = DemuxedAudioTrack | DemuxedVideoTrack; | |
| type RemuxedTrackType = RemuxedAudioTrackSamples | RemuxedVideoTrackSamples; | |
| type HdlrTypes = { | |
| video: Uint8Array; | |
| audio: Uint8Array; | |
| }; | |
| const UINT32_MAX = Math.pow(2, 32) - 1; | |
| class MP4 { | |
| public static types: Record<string, number[]>; | |
| private static HDLR_TYPES: HdlrTypes; | |
| private static STTS: Uint8Array; | |
| private static STSC: Uint8Array; | |
| private static STCO: Uint8Array; | |
| private static STSZ: Uint8Array; | |
| private static VMHD: Uint8Array; | |
| private static SMHD: Uint8Array; | |
| private static STSD: Uint8Array; | |
| private static FTYP: Uint8Array; | |
| private static DINF: Uint8Array; | |
| static init() { | |
| MP4.types = { | |
| avc1: [], // codingname | |
| avcC: [], | |
| hvc1: [], | |
| hvcC: [], | |
| btrt: [], | |
| dinf: [], | |
| dref: [], | |
| esds: [], | |
| ftyp: [], | |
| hdlr: [], | |
| mdat: [], | |
| mdhd: [], | |
| mdia: [], | |
| mfhd: [], | |
| minf: [], | |
| moof: [], | |
| moov: [], | |
| mp4a: [], | |
| '.mp3': [], | |
| dac3: [], | |
| 'ac-3': [], | |
| mvex: [], | |
| mvhd: [], | |
| pasp: [], | |
| sdtp: [], | |
| stbl: [], | |
| stco: [], | |
| stsc: [], | |
| stsd: [], | |
| stsz: [], | |
| stts: [], | |
| tfdt: [], | |
| tfhd: [], | |
| traf: [], | |
| trak: [], | |
| trun: [], | |
| trex: [], | |
| tkhd: [], | |
| vmhd: [], | |
| smhd: [], | |
| }; | |
| let i: string; | |
| for (i in MP4.types) { | |
| if (MP4.types.hasOwnProperty(i)) { | |
| MP4.types[i] = [ | |
| i.charCodeAt(0), | |
| i.charCodeAt(1), | |
| i.charCodeAt(2), | |
| i.charCodeAt(3), | |
| ]; | |
| } | |
| } | |
| const videoHdlr = new Uint8Array([ | |
| 0x00, // version 0 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // pre_defined | |
| 0x76, | |
| 0x69, | |
| 0x64, | |
| 0x65, // handler_type: 'vide' | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x56, | |
| 0x69, | |
| 0x64, | |
| 0x65, | |
| 0x6f, | |
| 0x48, | |
| 0x61, | |
| 0x6e, | |
| 0x64, | |
| 0x6c, | |
| 0x65, | |
| 0x72, | |
| 0x00, // name: 'VideoHandler' | |
| ]); | |
| const audioHdlr = new Uint8Array([ | |
| 0x00, // version 0 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // pre_defined | |
| 0x73, | |
| 0x6f, | |
| 0x75, | |
| 0x6e, // handler_type: 'soun' | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x53, | |
| 0x6f, | |
| 0x75, | |
| 0x6e, | |
| 0x64, | |
| 0x48, | |
| 0x61, | |
| 0x6e, | |
| 0x64, | |
| 0x6c, | |
| 0x65, | |
| 0x72, | |
| 0x00, // name: 'SoundHandler' | |
| ]); | |
| MP4.HDLR_TYPES = { | |
| video: videoHdlr, | |
| audio: audioHdlr, | |
| }; | |
| const dref = new Uint8Array([ | |
| 0x00, // version 0 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x01, // entry_count | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x0c, // entry_size | |
| 0x75, | |
| 0x72, | |
| 0x6c, | |
| 0x20, // 'url' type | |
| 0x00, // version 0 | |
| 0x00, | |
| 0x00, | |
| 0x01, // entry_flags | |
| ]); | |
| const stco = new Uint8Array([ | |
| 0x00, // version | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // entry_count | |
| ]); | |
| MP4.STTS = MP4.STSC = MP4.STCO = stco; | |
| MP4.STSZ = new Uint8Array([ | |
| 0x00, // version | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // sample_size | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // sample_count | |
| ]); | |
| MP4.VMHD = new Uint8Array([ | |
| 0x00, // version | |
| 0x00, | |
| 0x00, | |
| 0x01, // flags | |
| 0x00, | |
| 0x00, // graphicsmode | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // opcolor | |
| ]); | |
| MP4.SMHD = new Uint8Array([ | |
| 0x00, // version | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, // balance | |
| 0x00, | |
| 0x00, // reserved | |
| ]); | |
| MP4.STSD = new Uint8Array([ | |
| 0x00, // version 0 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x01, | |
| ]); // entry_count | |
| const majorBrand = new Uint8Array([105, 115, 111, 109]); // isom | |
| const avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1 | |
| const minorVersion = new Uint8Array([0, 0, 0, 1]); | |
| MP4.FTYP = MP4.box( | |
| MP4.types.ftyp, | |
| majorBrand, | |
| minorVersion, | |
| majorBrand, | |
| avc1Brand, | |
| ); | |
| MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref)); | |
| } | |
| static box(type: number[], ...payload: Uint8Array[]) { | |
| let size = 8; | |
| let i = payload.length; | |
| const len = i; | |
| // calculate the total size we need to allocate | |
| while (i--) { | |
| size += payload[i].byteLength; | |
| } | |
| const result = new Uint8Array(size); | |
| result[0] = (size >> 24) & 0xff; | |
| result[1] = (size >> 16) & 0xff; | |
| result[2] = (size >> 8) & 0xff; | |
| result[3] = size & 0xff; | |
| result.set(type, 4); | |
| // copy the payload into the result | |
| for (i = 0, size = 8; i < len; i++) { | |
| // copy payload[i] array @ offset size | |
| result.set(payload[i], size); | |
| size += payload[i].byteLength; | |
| } | |
| return result; | |
| } | |
| static hdlr(type: keyof HdlrTypes) { | |
| return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]); | |
| } | |
| static mdat(data: Uint8Array) { | |
| return MP4.box(MP4.types.mdat, data); | |
| } | |
| static mdhd(timescale: number, duration: number) { | |
| duration *= timescale; | |
| const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); | |
| const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); | |
| return MP4.box( | |
| MP4.types.mdhd, | |
| new Uint8Array([ | |
| 0x01, // version 1 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x02, // creation_time | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x03, // modification_time | |
| (timescale >> 24) & 0xff, | |
| (timescale >> 16) & 0xff, | |
| (timescale >> 8) & 0xff, | |
| timescale & 0xff, // timescale | |
| upperWordDuration >> 24, | |
| (upperWordDuration >> 16) & 0xff, | |
| (upperWordDuration >> 8) & 0xff, | |
| upperWordDuration & 0xff, | |
| lowerWordDuration >> 24, | |
| (lowerWordDuration >> 16) & 0xff, | |
| (lowerWordDuration >> 8) & 0xff, | |
| lowerWordDuration & 0xff, | |
| 0x55, | |
| 0xc4, // 'und' language (undetermined) | |
| 0x00, | |
| 0x00, | |
| ]), | |
| ); | |
| } | |
| static mdia(track: MediaTrackType) { | |
| return MP4.box( | |
| MP4.types.mdia, | |
| MP4.mdhd(track.timescale || 0, track.duration || 0), | |
| MP4.hdlr(track.type), | |
| MP4.minf(track), | |
| ); | |
| } | |
| static mfhd(sequenceNumber: number) { | |
| return MP4.box( | |
| MP4.types.mfhd, | |
| new Uint8Array([ | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| sequenceNumber >> 24, | |
| (sequenceNumber >> 16) & 0xff, | |
| (sequenceNumber >> 8) & 0xff, | |
| sequenceNumber & 0xff, // sequence_number | |
| ]), | |
| ); | |
| } | |
| static minf(track: MediaTrackType) { | |
| if (track.type === 'audio') { | |
| return MP4.box( | |
| MP4.types.minf, | |
| MP4.box(MP4.types.smhd, MP4.SMHD), | |
| MP4.DINF, | |
| MP4.stbl(track), | |
| ); | |
| } else { | |
| return MP4.box( | |
| MP4.types.minf, | |
| MP4.box(MP4.types.vmhd, MP4.VMHD), | |
| MP4.DINF, | |
| MP4.stbl(track), | |
| ); | |
| } | |
| } | |
| static moof( | |
| sn: number, | |
| baseMediaDecodeTime: number, | |
| track: RemuxedTrackType, | |
| ) { | |
| return MP4.box( | |
| MP4.types.moof, | |
| MP4.mfhd(sn), | |
| MP4.traf(track, baseMediaDecodeTime), | |
| ); | |
| } | |
| static moov(tracks: MediaTrackType[]) { | |
| let i = tracks.length; | |
| const boxes: Uint8Array[] = []; | |
| while (i--) { | |
| boxes[i] = MP4.trak(tracks[i]); | |
| } | |
| return MP4.box.apply( | |
| null, | |
| [ | |
| MP4.types.moov, | |
| MP4.mvhd(tracks[0].timescale || 0, tracks[0].duration || 0), | |
| ] | |
| .concat(boxes) | |
| .concat(MP4.mvex(tracks)), | |
| ); | |
| } | |
| static mvex(tracks: MediaTrackType[]) { | |
| let i = tracks.length; | |
| const boxes: Uint8Array[] = []; | |
| while (i--) { | |
| boxes[i] = MP4.trex(tracks[i]); | |
| } | |
| return MP4.box.apply(null, [MP4.types.mvex, ...boxes]); | |
| } | |
| static mvhd(timescale: number, duration: number) { | |
| duration *= timescale; | |
| const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); | |
| const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); | |
| const bytes = new Uint8Array([ | |
| 0x01, // version 1 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x02, // creation_time | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x03, // modification_time | |
| (timescale >> 24) & 0xff, | |
| (timescale >> 16) & 0xff, | |
| (timescale >> 8) & 0xff, | |
| timescale & 0xff, // timescale | |
| upperWordDuration >> 24, | |
| (upperWordDuration >> 16) & 0xff, | |
| (upperWordDuration >> 8) & 0xff, | |
| upperWordDuration & 0xff, | |
| lowerWordDuration >> 24, | |
| (lowerWordDuration >> 16) & 0xff, | |
| (lowerWordDuration >> 8) & 0xff, | |
| lowerWordDuration & 0xff, | |
| 0x00, | |
| 0x01, | |
| 0x00, | |
| 0x00, // 1.0 rate | |
| 0x01, | |
| 0x00, // 1.0 volume | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 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 | |
| 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 | |
| 0xff, | |
| 0xff, | |
| 0xff, | |
| 0xff, // next_track_ID | |
| ]); | |
| return MP4.box(MP4.types.mvhd, bytes); | |
| } | |
| static sdtp(track: RemuxedTrackType) { | |
| const samples = track.samples || []; | |
| const bytes = new Uint8Array(4 + samples.length); | |
| let i: number; | |
| let flags: Mp4SampleFlags; | |
| // leave the full box header (4 bytes) all zero | |
| // write the sample table | |
| for (i = 0; i < samples.length; i++) { | |
| flags = samples[i].flags; | |
| bytes[i + 4] = | |
| (flags.dependsOn << 4) | | |
| (flags.isDependedOn << 2) | | |
| flags.hasRedundancy; | |
| } | |
| return MP4.box(MP4.types.sdtp, bytes); | |
| } | |
| static stbl(track: MediaTrackType) { | |
| 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), | |
| ); | |
| } | |
| static avc1(track: DemuxedAVC1) { | |
| let sps: number[] = []; | |
| let pps: number[] = []; | |
| let i; | |
| let data; | |
| let len; | |
| // assemble the SPSs | |
| for (i = 0; i < track.sps.length; i++) { | |
| data = track.sps[i]; | |
| len = data.byteLength; | |
| sps.push((len >>> 8) & 0xff); | |
| sps.push(len & 0xff); | |
| // SPS | |
| sps = sps.concat(Array.prototype.slice.call(data)); | |
| } | |
| // assemble the PPSs | |
| for (i = 0; i < track.pps.length; i++) { | |
| data = track.pps[i]; | |
| len = data.byteLength; | |
| pps.push((len >>> 8) & 0xff); | |
| pps.push(len & 0xff); | |
| pps = pps.concat(Array.prototype.slice.call(data)); | |
| } | |
| const avcc = MP4.box( | |
| MP4.types.avcC, | |
| new Uint8Array( | |
| [ | |
| 0x01, // version | |
| sps[3], // profile | |
| sps[4], // profile compat | |
| sps[5], // level | |
| 0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes | |
| 0xe0 | track.sps.length, // 3bit reserved (111) + numOfSequenceParameterSets | |
| ] | |
| .concat(sps) | |
| .concat([ | |
| track.pps.length, // numOfPictureParameterSets | |
| ]) | |
| .concat(pps), | |
| ), | |
| ); // "PPS" | |
| const width = track.width; | |
| const height = track.height; | |
| const hSpacing = track.pixelRatio[0]; | |
| const vSpacing = track.pixelRatio[1]; | |
| return MP4.box( | |
| MP4.types.avc1, | |
| new Uint8Array([ | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x01, // data_reference_index | |
| 0x00, | |
| 0x00, // pre_defined | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // pre_defined | |
| (width >> 8) & 0xff, | |
| width & 0xff, // width | |
| (height >> 8) & 0xff, | |
| height & 0xff, // height | |
| 0x00, | |
| 0x48, | |
| 0x00, | |
| 0x00, // horizresolution | |
| 0x00, | |
| 0x48, | |
| 0x00, | |
| 0x00, // vertresolution | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x01, // frame_count | |
| 0x12, | |
| 0x64, | |
| 0x61, | |
| 0x69, | |
| 0x6c, // dailymotion/hls.js | |
| 0x79, | |
| 0x6d, | |
| 0x6f, | |
| 0x74, | |
| 0x69, | |
| 0x6f, | |
| 0x6e, | |
| 0x2f, | |
| 0x68, | |
| 0x6c, | |
| 0x73, | |
| 0x2e, | |
| 0x6a, | |
| 0x73, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // compressorname | |
| 0x00, | |
| 0x18, // depth = 24 | |
| 0x11, | |
| 0x11, | |
| ]), // pre_defined = -1 | |
| avcc, | |
| MP4.box( | |
| MP4.types.btrt, | |
| new Uint8Array([ | |
| 0x00, | |
| 0x1c, | |
| 0x9c, | |
| 0x80, // bufferSizeDB | |
| 0x00, | |
| 0x2d, | |
| 0xc6, | |
| 0xc0, // maxBitrate | |
| 0x00, | |
| 0x2d, | |
| 0xc6, | |
| 0xc0, | |
| ]), | |
| ), // avgBitrate | |
| MP4.box( | |
| MP4.types.pasp, | |
| new Uint8Array([ | |
| hSpacing >> 24, // hSpacing | |
| (hSpacing >> 16) & 0xff, | |
| (hSpacing >> 8) & 0xff, | |
| hSpacing & 0xff, | |
| vSpacing >> 24, // vSpacing | |
| (vSpacing >> 16) & 0xff, | |
| (vSpacing >> 8) & 0xff, | |
| vSpacing & 0xff, | |
| ]), | |
| ), | |
| ); | |
| } | |
| static esds(track: DemuxedAudioTrack) { | |
| const config = track.config as [number, number]; | |
| return new Uint8Array([ | |
| 0x00, // version 0 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| 0x03, // descriptor_type | |
| 0x19, // length | |
| 0x00, | |
| 0x01, // es_id | |
| 0x00, // stream_priority | |
| 0x04, // descriptor_type | |
| 0x11, // length | |
| 0x40, // codec : mpeg4_audio | |
| 0x15, // stream_type | |
| 0x00, | |
| 0x00, | |
| 0x00, // buffer_size | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // maxBitrate | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // avgBitrate | |
| 0x05, // descriptor_type | |
| 0x02, // length | |
| ...config, | |
| 0x06, | |
| 0x01, | |
| 0x02, // GASpecificConfig)); // length + audio config descriptor | |
| ]); | |
| } | |
| static audioStsd(track: DemuxedAudioTrack) { | |
| const samplerate = track.samplerate || 0; | |
| return new Uint8Array([ | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x01, // data_reference_index | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| track.channelCount || 0, // channelcount | |
| 0x00, | |
| 0x10, // sampleSize:16bits | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved2 | |
| (samplerate >> 8) & 0xff, | |
| samplerate & 0xff, // | |
| 0x00, | |
| 0x00, | |
| ]); | |
| } | |
| static mp4a(track: DemuxedAudioTrack) { | |
| return MP4.box( | |
| MP4.types.mp4a, | |
| MP4.audioStsd(track), | |
| MP4.box(MP4.types.esds, MP4.esds(track)), | |
| ); | |
| } | |
| static mp3(track: DemuxedAudioTrack) { | |
| return MP4.box(MP4.types['.mp3'], MP4.audioStsd(track)); | |
| } | |
| static ac3(track: DemuxedAudioTrack) { | |
| return MP4.box( | |
| MP4.types['ac-3'], | |
| MP4.audioStsd(track), | |
| MP4.box(MP4.types.dac3, track.config as Uint8Array), | |
| ); | |
| } | |
| static stsd(track: MediaTrackType | DemuxedAC3): Uint8Array { | |
| const { segmentCodec } = track; | |
| if (track.type === 'audio') { | |
| if (segmentCodec === 'aac') { | |
| return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track)); | |
| } | |
| if ( | |
| __USE_M2TS_ADVANCED_CODECS__ && | |
| segmentCodec === 'ac3' && | |
| track.config | |
| ) { | |
| return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track)); | |
| } | |
| if (segmentCodec === 'mp3' && track.codec === 'mp3') { | |
| return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track)); | |
| } | |
| } else { | |
| if (track.pps && track.sps) { | |
| if (segmentCodec === 'avc') { | |
| return MP4.box( | |
| MP4.types.stsd, | |
| MP4.STSD, | |
| MP4.avc1(track as DemuxedAVC1), | |
| ); | |
| } | |
| if ( | |
| __USE_M2TS_ADVANCED_CODECS__ && | |
| segmentCodec === 'hevc' && | |
| track.vps | |
| ) { | |
| return MP4.box( | |
| MP4.types.stsd, | |
| MP4.STSD, | |
| MP4.hvc1(track as DemuxedHEVC), | |
| ); | |
| } | |
| } else { | |
| throw new Error(`video track missing pps or sps`); | |
| } | |
| } | |
| throw new Error( | |
| `unsupported ${track.type} segment codec (${segmentCodec}/${track.codec})`, | |
| ); | |
| } | |
| static tkhd(track: MediaTrackType) { | |
| const id = track.id; | |
| const duration = (track.duration || 0) * (track.timescale || 0); | |
| const width = (track as any).width || 0; | |
| const height = (track as any).height || 0; | |
| const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); | |
| const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); | |
| return MP4.box( | |
| MP4.types.tkhd, | |
| new Uint8Array([ | |
| 0x01, // version 1 | |
| 0x00, | |
| 0x00, | |
| 0x07, // flags | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x02, // creation_time | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x03, // modification_time | |
| (id >> 24) & 0xff, | |
| (id >> 16) & 0xff, | |
| (id >> 8) & 0xff, | |
| id & 0xff, // track_ID | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| upperWordDuration >> 24, | |
| (upperWordDuration >> 16) & 0xff, | |
| (upperWordDuration >> 8) & 0xff, | |
| upperWordDuration & 0xff, | |
| lowerWordDuration >> 24, | |
| (lowerWordDuration >> 16) & 0xff, | |
| (lowerWordDuration >> 8) & 0xff, | |
| lowerWordDuration & 0xff, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, // layer | |
| 0x00, | |
| 0x00, // alternate_group | |
| 0x00, | |
| 0x00, // non-audio track volume | |
| 0x00, | |
| 0x00, // reserved | |
| 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 | |
| (width >> 8) & 0xff, | |
| width & 0xff, | |
| 0x00, | |
| 0x00, // width | |
| (height >> 8) & 0xff, | |
| height & 0xff, | |
| 0x00, | |
| 0x00, // height | |
| ]), | |
| ); | |
| } | |
| static traf(track: RemuxedTrackType, baseMediaDecodeTime: number) { | |
| const sampleDependencyTable = MP4.sdtp(track); | |
| const id = track.id; | |
| const upperWordBaseMediaDecodeTime = Math.floor( | |
| baseMediaDecodeTime / (UINT32_MAX + 1), | |
| ); | |
| const lowerWordBaseMediaDecodeTime = Math.floor( | |
| baseMediaDecodeTime % (UINT32_MAX + 1), | |
| ); | |
| return MP4.box( | |
| MP4.types.traf, | |
| MP4.box( | |
| MP4.types.tfhd, | |
| new Uint8Array([ | |
| 0x00, // version 0 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| id >> 24, | |
| (id >> 16) & 0xff, | |
| (id >> 8) & 0xff, | |
| id & 0xff, // track_ID | |
| ]), | |
| ), | |
| MP4.box( | |
| MP4.types.tfdt, | |
| new Uint8Array([ | |
| 0x01, // version 1 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| upperWordBaseMediaDecodeTime >> 24, | |
| (upperWordBaseMediaDecodeTime >> 16) & 0xff, | |
| (upperWordBaseMediaDecodeTime >> 8) & 0xff, | |
| upperWordBaseMediaDecodeTime & 0xff, | |
| lowerWordBaseMediaDecodeTime >> 24, | |
| (lowerWordBaseMediaDecodeTime >> 16) & 0xff, | |
| (lowerWordBaseMediaDecodeTime >> 8) & 0xff, | |
| lowerWordBaseMediaDecodeTime & 0xff, | |
| ]), | |
| ), | |
| MP4.trun( | |
| track, | |
| sampleDependencyTable.length + | |
| 16 + // tfhd | |
| 20 + // tfdt | |
| 8 + // traf header | |
| 16 + // mfhd | |
| 8 + // moof header | |
| 8, | |
| ), // mdat header | |
| sampleDependencyTable, | |
| ); | |
| } | |
| /** | |
| * Generate a track box. | |
| * @param track a track definition | |
| */ | |
| static trak(track: MediaTrackType) { | |
| track.duration = track.duration || 0xffffffff; | |
| return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track)); | |
| } | |
| static trex(track: MediaTrackType) { | |
| const id = track.id; | |
| return MP4.box( | |
| MP4.types.trex, | |
| new Uint8Array([ | |
| 0x00, // version 0 | |
| 0x00, | |
| 0x00, | |
| 0x00, // flags | |
| id >> 24, | |
| (id >> 16) & 0xff, | |
| (id >> 8) & 0xff, | |
| id & 0xff, // track_ID | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x01, // default_sample_description_index | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // default_sample_duration | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // default_sample_size | |
| 0x00, | |
| 0x01, | |
| 0x00, | |
| 0x01, // default_sample_flags | |
| ]), | |
| ); | |
| } | |
| static trun(track: MediaTrackType, offset: number) { | |
| const samples = track.samples || []; | |
| const len = samples.length; | |
| const arraylen = 12 + 16 * len; | |
| const array = new Uint8Array(arraylen); | |
| let i; | |
| let sample; | |
| let duration; | |
| let size; | |
| let flags; | |
| let cts; | |
| offset += 8 + arraylen; | |
| array.set( | |
| [ | |
| track.type === 'video' ? 0x01 : 0x00, // version 1 for video with signed-int sample_composition_time_offset | |
| 0x00, | |
| 0x0f, | |
| 0x01, // flags | |
| (len >>> 24) & 0xff, | |
| (len >>> 16) & 0xff, | |
| (len >>> 8) & 0xff, | |
| len & 0xff, // sample_count | |
| (offset >>> 24) & 0xff, | |
| (offset >>> 16) & 0xff, | |
| (offset >>> 8) & 0xff, | |
| offset & 0xff, // data_offset | |
| ], | |
| 0, | |
| ); | |
| for (i = 0; i < len; i++) { | |
| sample = samples[i]; | |
| duration = sample.duration; | |
| size = sample.size; | |
| flags = sample.flags; | |
| cts = sample.cts; | |
| array.set( | |
| [ | |
| (duration >>> 24) & 0xff, | |
| (duration >>> 16) & 0xff, | |
| (duration >>> 8) & 0xff, | |
| duration & 0xff, // sample_duration | |
| (size >>> 24) & 0xff, | |
| (size >>> 16) & 0xff, | |
| (size >>> 8) & 0xff, | |
| size & 0xff, // sample_size | |
| (flags.isLeading << 2) | flags.dependsOn, | |
| (flags.isDependedOn << 6) | | |
| (flags.hasRedundancy << 4) | | |
| (flags.paddingValue << 1) | | |
| flags.isNonSync, | |
| flags.degradPrio & (0xf0 << 8), | |
| flags.degradPrio & 0x0f, // sample_flags | |
| (cts >>> 24) & 0xff, | |
| (cts >>> 16) & 0xff, | |
| (cts >>> 8) & 0xff, | |
| cts & 0xff, // sample_composition_time_offset | |
| ], | |
| 12 + 16 * i, | |
| ); | |
| } | |
| return MP4.box(MP4.types.trun, array); | |
| } | |
| static initSegment(tracks: MediaTrackType[]) { | |
| if (!MP4.types) { | |
| MP4.init(); | |
| } | |
| const movie = MP4.moov(tracks); | |
| const result = appendUint8Array(MP4.FTYP, movie); | |
| return result; | |
| } | |
| static hvc1(track: DemuxedHEVC) { | |
| if (!__USE_M2TS_ADVANCED_CODECS__) { | |
| return new Uint8Array(); | |
| } | |
| const ps = track.params; | |
| const units: Uint8Array[][] = [track.vps, track.sps, track.pps]; | |
| const NALuLengthSize = 4; | |
| const config = new Uint8Array([ | |
| 0x01, | |
| (ps.general_profile_space << 6) | | |
| (ps.general_tier_flag ? 32 : 0) | | |
| ps.general_profile_idc, | |
| ps.general_profile_compatibility_flags[0], | |
| ps.general_profile_compatibility_flags[1], | |
| ps.general_profile_compatibility_flags[2], | |
| ps.general_profile_compatibility_flags[3], | |
| ps.general_constraint_indicator_flags[0], | |
| ps.general_constraint_indicator_flags[1], | |
| ps.general_constraint_indicator_flags[2], | |
| ps.general_constraint_indicator_flags[3], | |
| ps.general_constraint_indicator_flags[4], | |
| ps.general_constraint_indicator_flags[5], | |
| ps.general_level_idc, | |
| 240 | (ps.min_spatial_segmentation_idc >> 8), | |
| 255 & ps.min_spatial_segmentation_idc, | |
| 252 | ps.parallelismType, | |
| 252 | ps.chroma_format_idc, | |
| 248 | ps.bit_depth_luma_minus8, | |
| 248 | ps.bit_depth_chroma_minus8, | |
| 0x00, | |
| parseInt(ps.frame_rate.fps), | |
| (NALuLengthSize - 1) | | |
| (ps.temporal_id_nested << 2) | | |
| (ps.num_temporal_layers << 3) | | |
| (ps.frame_rate.fixed ? 64 : 0), | |
| units.length, | |
| ]); | |
| // compute hvcC size in bytes | |
| let length = config.length; | |
| for (let i = 0; i < units.length; i += 1) { | |
| length += 3; | |
| for (let j = 0; j < units[i].length; j += 1) { | |
| length += 2 + units[i][j].length; | |
| } | |
| } | |
| const hvcC = new Uint8Array(length); | |
| hvcC.set(config, 0); | |
| length = config.length; | |
| // append parameter set units: one vps, one or more sps and pps | |
| const iMax = units.length - 1; | |
| for (let i = 0; i < units.length; i += 1) { | |
| hvcC.set( | |
| new Uint8Array([ | |
| (32 + i) | (i === iMax ? 128 : 0), | |
| 0x00, | |
| units[i].length, | |
| ]), | |
| length, | |
| ); | |
| length += 3; | |
| for (let j = 0; j < units[i].length; j += 1) { | |
| hvcC.set( | |
| new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), | |
| length, | |
| ); | |
| length += 2; | |
| hvcC.set(units[i][j], length); | |
| length += units[i][j].length; | |
| } | |
| } | |
| const hvcc = MP4.box(MP4.types.hvcC, hvcC); | |
| const width = track.width; | |
| const height = track.height; | |
| const hSpacing = track.pixelRatio[0]; | |
| const vSpacing = track.pixelRatio[1]; | |
| return MP4.box( | |
| MP4.types.hvc1, | |
| new Uint8Array([ | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x01, // data_reference_index | |
| 0x00, | |
| 0x00, // pre_defined | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // pre_defined | |
| (width >> 8) & 0xff, | |
| width & 0xff, // width | |
| (height >> 8) & 0xff, | |
| height & 0xff, // height | |
| 0x00, | |
| 0x48, | |
| 0x00, | |
| 0x00, // horizresolution | |
| 0x00, | |
| 0x48, | |
| 0x00, | |
| 0x00, // vertresolution | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // reserved | |
| 0x00, | |
| 0x01, // frame_count | |
| 0x12, | |
| 0x64, | |
| 0x61, | |
| 0x69, | |
| 0x6c, // dailymotion/hls.js | |
| 0x79, | |
| 0x6d, | |
| 0x6f, | |
| 0x74, | |
| 0x69, | |
| 0x6f, | |
| 0x6e, | |
| 0x2f, | |
| 0x68, | |
| 0x6c, | |
| 0x73, | |
| 0x2e, | |
| 0x6a, | |
| 0x73, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, | |
| 0x00, // compressorname | |
| 0x00, | |
| 0x18, // depth = 24 | |
| 0x11, | |
| 0x11, | |
| ]), // pre_defined = -1 | |
| hvcc, | |
| MP4.box( | |
| MP4.types.btrt, | |
| new Uint8Array([ | |
| 0x00, | |
| 0x1c, | |
| 0x9c, | |
| 0x80, // bufferSizeDB | |
| 0x00, | |
| 0x2d, | |
| 0xc6, | |
| 0xc0, // maxBitrate | |
| 0x00, | |
| 0x2d, | |
| 0xc6, | |
| 0xc0, | |
| ]), | |
| ), // avgBitrate | |
| MP4.box( | |
| MP4.types.pasp, | |
| new Uint8Array([ | |
| hSpacing >> 24, // hSpacing | |
| (hSpacing >> 16) & 0xff, | |
| (hSpacing >> 8) & 0xff, | |
| hSpacing & 0xff, | |
| vSpacing >> 24, // vSpacing | |
| (vSpacing >> 16) & 0xff, | |
| (vSpacing >> 8) & 0xff, | |
| vSpacing & 0xff, | |
| ]), | |
| ), | |
| ); | |
| } | |
| } | |
| export default MP4; | |
Xet Storage Details
- Size:
- 29.9 kB
- Xet hash:
- 33813101a0b2b7850bdaddfd871b2a2713aa3a229370ea7057ae61fd93377756
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.