import { VideoTrack } from './videoData' ; import { AudioTrack } from './audioData' ; export type Track = AudioTrack | VideoTrack ; const boxtypes = new Map < string , number > ( ) ; function toInt ( s ) { let n = 0 ; for ( let i = 0 , l = s . length ; i < l ; i ++ ) { n = ( n << 8 ) | s . charCodeAt ( i ) ; } return n ; } type Box = { byteLength : number ; box : Uint8Array [ ] ; } ; function box ( type , ... payload ) { let size = 8 ; const header = new Uint8Array ( 8 ) ; const view = new DataView ( header . buffer ) ; const box = [ header ] ; if ( ! boxtypes . has ( type ) ) { boxtypes . set ( type , toInt ( type ) ) ; } for ( const p of payload ) { size += p . byteLength ; if ( p instanceof Uint8Array ) box . push ( p ) ; else box . push ( ... p . box ) ; } view . setUint32 ( 0 , size ) ; view . setUint32 ( 4 , boxtypes . get ( type ) as number ) ; return { byteLength : size , box } ; } function merge ( ... boxes ) { const size = boxes . reduce ( ( a , n ) => a + n . byteLength , 0 ) ; const arr = new Uint8Array ( size ) ; let i = 0 ; for ( const b of boxes ) { for ( const chunk of b . box ) { arr . set ( chunk , i ) ; i += chunk . byteLength ; } } return arr ; } function ftyp ( ) { const buffer = new Uint8Array ( 20 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 0 , 0x69736f6d ) ; view . setUint32 ( 4 , 1 ) ; view . setUint32 ( 8 , 0x69736f6d ) ; view . setUint32 ( 12 , 0x61766331 ) ; view . setUint32 ( 16 , 0x6d703431 ) ; return box ( 'ftyp' , buffer ) ; } function mdat ( tracks ) { return box ( 'mdat' , ... tracks . map ( track => track . data ) ) ; } /** MOOV SECTION
  moov
    mvhd
    trak
      tkhd
      mdia
        mdhd
        hdlr
        minf
          smhd / vmhd
          dinf > dref
          stbl
            stsd
              mp4a > esds
              avc1 > avcC
            stts
            stsc
            stsz
            stco
            stss
            ctts
**/ function hdlr ( track ) { const buffer = new Uint8Array ( 37 ) ; const view = new DataView ( buffer . buffer ) ; if ( track . type === 'video' ) { view . setUint32 ( 8 , 0x76696465 ) ; view . setUint32 ( 24 , 0x56696465 ) ; view . setUint32 ( 28 , 0x6f48616e ) ; } else { view . setUint32 ( 8 , 0x736f756e ) ; view . setUint32 ( 24 , 0x536f756e ) ; view . setUint32 ( 28 , 0x6448616e ) ; } view . setUint32 ( 32 , 0x646c6572 ) ; return box ( 'hdlr' , buffer ) ; } function vmhd ( ) { const buffer = new Uint8Array ( 12 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 0 , 1 ) ; return box ( 'vmhd' , buffer ) ; } function smhd ( ) { return box ( 'smhd' , new Uint8Array ( 8 ) ) ; } function dref ( ) { const buffer = new Uint8Array ( 21 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 4 , 1 ) ; view . setUint32 ( 8 , 13 ) ; view . setUint32 ( 12 , 0x75726c20 ) ; view . setUint32 ( 16 , 1 ) ; return box ( 'dref' , buffer ) ; } function dinf ( ) { return box ( 'dinf' , dref ( ) ) ; } function avcC ( track ) { const { sps , pps , spsInfo } = track ; const spslen = sps . byteLength ; const ppslen = pps . byteLength ; const buffer = new Uint8Array ( spslen + ppslen + 11 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint8 ( 0 , 1 ) ; view . setUint8 ( 1 , spsInfo . profile_idc ) ; view . setUint8 ( 2 , spsInfo . profile_compatibility ) ; view . setUint8 ( 3 , spsInfo . level_idc ) ; view . setUint8 ( 4 , 0xff ) ; view . setUint8 ( 5 , 0xe1 ) ; view . setUint16 ( 6 , spslen ) ; for ( let i = 0 , j = 8 ; i < spslen ; i ++ , j ++ ) { view . setUint8 ( j , sps [ i ] ) ; } view . setUint8 ( 8 + spslen , 1 ) ; view . setUint16 ( 9 + spslen , ppslen ) ; for ( let i = 0 , j = 11 + spslen ; i < ppslen ; i ++ , j ++ ) { view . setUint8 ( j , pps [ i ] ) ; } return box ( 'avcC' , buffer ) ; } function avc1 ( track ) { const buffer = new Uint8Array ( 78 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint16 ( 6 , 1 ) ; view . setUint16 ( 24 , track . width ) ; view . setUint16 ( 26 , track . height ) ; view . setUint32 ( 28 , 0x00480000 ) ; view . setUint32 ( 32 , 0x00480000 ) ; view . setUint16 ( 40 , 1 ) ; view . setUint16 ( 74 , 24 ) ; view . setUint16 ( 76 , 0xffff ) ; return box ( 'avc1' , buffer , avcC ( track ) ) ; } function esds ( track ) { const buffer = new Uint8Array ( 43 ) ; const view = new DataView ( buffer . buffer ) ; const freqIndex = track . samplingFreqIndex ; const objectType = track . profileMinusOne + 1 ; const channelConf = track . channelConfig ; view . setUint32 ( 4 , 0x03808080 ) ; view . setUint32 ( 8 , 0x22000200 ) ; view . setUint32 ( 12 , 0x04808080 ) ; view . setUint32 ( 16 , 0x14401500 ) ; view . setUint32 ( 22 , track . maxBitrate ) ; view . setUint32 ( 26 , track . avgBitrate ) ; view . setUint32 ( 30 , 0x05808080 ) ; view . setUint8 ( 34 , 2 ) ; view . setUint16 ( 35 , ( objectType << 11 ) | ( freqIndex << 7 ) | ( channelConf << 3 ) ) ; view . setUint32 ( 37 , 0x06808080 ) ; view . setUint16 ( 41 , 0x0102 ) ; return box ( 'esds' , buffer ) ; } function channelCount ( conf ) { if ( conf < 2 ) { return 1 ; } if ( conf < 7 ) { return conf ; } return 8 ; } const sampleRates = [ 96000 , 88200 , 64000 , 48000 , 44100 , 32000 , 24000 , 22050 , 16000 , 12000 , 11025 , 8000 , 7350 , ] ; function mp4a ( track ) { const buffer = new Uint8Array ( 28 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint16 ( 6 , 1 ) ; view . setUint16 ( 16 , channelCount ( track . channelConfig ) ) ; view . setUint16 ( 18 , 16 ) ; view . setUint32 ( 24 , sampleRates [ track . samplingFreqIndex ] << 16 ) ; return box ( 'mp4a' , buffer , esds ( track ) ) ; } function stsd ( track ) { const buffer = new Uint8Array ( 8 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 4 , 1 ) ; return box ( 'stsd' , buffer , track . type === 'video' ? avc1 ( track ) : mp4a ( track ) ) ; } function stts ( track ) { const dts_diffs = [ ] ; let current = { sample_count : 0 , sample_delta : 0 } ; let last_delta = - 1 ; for ( const sample of track . samples ) { const delta = sample . duration ; if ( delta !== last_delta ) { current = { sample_count : 1 , sample_delta : delta } ; dts_diffs . push ( current ) ; last_delta = delta ; } else { current . sample_count ++ ; } } const c = dts_diffs . length ; const buffer = new Uint8Array ( c * 8 + 8 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 4 , c ) ; for ( let i = 0 , j = 8 ; i < c ; i ++ , j += 8 ) { view . setUint32 ( j , dts_diffs [ i ] . sample_count ) ; view . setUint32 ( j + 4 , dts_diffs [ i ] . sample_delta ) ; } return box ( 'stts' , buffer ) ; } function stsz ( track ) { const { samples } = track ; const c = samples . length ; const buffer = new Uint8Array ( c * 4 + 12 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 8 , c ) ; for ( let i = 0 , j = 12 ; i < c ; i ++ , j += 4 ) { view . setUint32 ( j , samples [ i ] . size ) ; } return box ( 'stsz' , buffer ) ; } function stsc ( track ) { const buffer = new Uint8Array ( 20 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 4 , 1 ) ; view . setUint32 ( 8 , 1 ) ; view . setUint32 ( 12 , track . samples . length ) ; view . setUint32 ( 16 , 1 ) ; return box ( 'stsc' , buffer ) ; } function stco ( track ) { const buffer = new Uint8Array ( 12 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 4 , 1 ) ; view . setUint32 ( 8 , track . byte_offset ) ; return box ( 'stco' , buffer ) ; } function stss ( track ) { const indices = track . samples . map ( ( s , i ) => s . isIDR ? i + 1 : - 1 ) . filter ( i => i !== - 1 ) ; const c = indices . length ; const buffer = new Uint8Array ( c * 4 + 8 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 4 , c ) ; for ( let i = 0 , j = 8 ; i < c ; i ++ , j += 4 ) { view . setUint32 ( j , indices [ i ] ) ; } return box ( 'stss' , buffer ) ; } function ctts ( track ) { const pd_diffs = [ ] ; let last_offset = - 1 ; let current = { sample_count : 1 , sample_offset : 0 , } ; for ( const s of track . samples ) { const offset = s . cts ; if ( offset === last_offset ) { current . sample_count ++ ; } else { last_offset = offset ; current = { sample_count : 1 , sample_offset : offset , } ; pd_diffs . push ( current ) ; } } const c = pd_diffs . length ; if ( c === 0 ) { return new Uint8Array ( 0 ) ; } const buffer = new Uint8Array ( c * 8 + 8 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 4 , c ) ; for ( let i = 0 , j = 8 ; i < c ; i ++ , j += 8 ) { view . setUint32 ( j , pd_diffs [ i ] . sample_count ) ; view . setUint32 ( j + 4 , pd_diffs [ i ] . sample_offset ) ; } return box ( 'ctts' , buffer ) ; } function stbl ( track ) { const subboxes = [ stsd , stts , stsc , stsz , stco ] ; if ( track . type === 'video' ) { subboxes . push ( stss ) ; subboxes . push ( ctts ) ; } return box ( 'stbl' , ... subboxes . map ( b => b ( track ) ) ) ; } function minf ( track ) { return box ( 'minf' , ( track . type === 'video' ? vmhd : smhd ) ( ) , dinf ( ) , stbl ( track ) ) ; } function mdhd ( track ) { const buffer = new Uint8Array ( 24 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 12 , 90000 ) ; view . setUint32 ( 16 , track . duration ) ; view . setUint32 ( 20 , 0x55c40000 ) ; return box ( 'mdhd' , buffer ) ; } function mdia ( track ) { return box ( 'mdia' , mdhd ( track ) , hdlr ( track ) , minf ( track ) ) ; } function tkhd ( track , id ) { const buffer = new Uint8Array ( 84 ) ; const view = new DataView ( buffer . buffer ) ; view . setUint32 ( 0 , 15 ) ; view . setUint32 ( 12 , id ) ; view . setUint32 ( 20 , track . duration || 0xffffffff ) ; view . setUint32 ( 36 , 0x01000000 ) ; view . setUint32 ( 52 , 0x00010000 ) ; view . setUint32 ( 72 , 0x40000000 ) ; if ( track . type === 'audio' ) { view . setUint32 ( 32 , 0x01000000 ) ; } else { view . setUint32 ( 76 , ( track . width & 0xffff ) << 16 ) ; view . setUint32 ( 80 , ( track . height & 0xffff ) << 16 ) ; } return box ( 'tkhd' , buffer ) ; } function trak ( track , id ) { return box ( 'trak' , tkhd ( track , id ) , mdia ( track ) ) ; } function mvhd ( tracks ) { const buffer = new Uint8Array ( 100 ) ; const view = new DataView ( buffer . buffer ) ; const d = Math . max ( ... tracks . map ( track => track . duration ) ) ; view . setUint32 ( 12 , 90000 ) ; view . setUint32 ( 16 , d ) ; view . setUint32 ( 20 , 0x00010000 ) ; view . setUint32 ( 24 , 0x01000000 ) ; view . setUint32 ( 36 , 0x00010000 ) ; view . setUint32 ( 42 , 0x00010000 ) ; view . setUint32 ( 68 , 0x40000000 ) ; view . setUint32 ( 96 , 0xffffffff ) ; return box ( 'mvhd' , buffer ) ; } function moov ( tracks ) { return box ( 'moov' , mvhd ( tracks ) , ... tracks . map ( ( track , i ) => trak ( track , i + 1 ) ) ) ; } export function MP4File ( tracks ) { let offset = 36 ; for ( const track of tracks ) { track . byte_offset = offset ; offset += track . data . byteLength ; } return merge ( ftyp ( ) , mdat ( tracks ) , moov ( tracks ) ) ; }