@l5i/dashjs
Version:
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
174 lines (173 loc) • 19.5 kB
JavaScript
/**
* The copyright in this software is being made available under the BSD License,
* included below. This software may be subject to other third party and contributor
* rights, including patent rights, and no such rights are granted under this license.
*
* Copyright (c) 2013, Dash Industry Forum.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* * Neither the name of Dash Industry Forum nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/ /**
* @module MssFragmentMoovProcessor
* @param {Object} config object
*/'use strict';Object.defineProperty(exports,'__esModule',{value:true});function MssFragmentMoovProcessor(config){config = config || {};var NALUTYPE_SPS=7;var NALUTYPE_PPS=8;var constants=config.constants;var ISOBoxer=config.ISOBoxer;var protectionController=config.protectionController;var instance=undefined,period=undefined,adaptationSet=undefined,representation=undefined,contentProtection=undefined,timescale=undefined,trackId=undefined;function createFtypBox(isoFile){var ftyp=ISOBoxer.createBox('ftyp',isoFile);ftyp.major_brand = 'iso6';ftyp.minor_version = 1; // is an informative integer for the minor version of the major brand
ftyp.compatible_brands = []; //is a list, to the end of the box, of brands isom, iso6 and msdh
ftyp.compatible_brands[0] = 'isom'; // => decimal ASCII value for isom
ftyp.compatible_brands[1] = 'iso6'; // => decimal ASCII value for iso6
ftyp.compatible_brands[2] = 'msdh'; // => decimal ASCII value for msdh
return ftyp;}function createMoovBox(isoFile){ // moov box
var moov=ISOBoxer.createBox('moov',isoFile); // moov/mvhd
createMvhdBox(moov); // moov/trak
var trak=ISOBoxer.createBox('trak',moov); // moov/trak/tkhd
createTkhdBox(trak); // moov/trak/mdia
var mdia=ISOBoxer.createBox('mdia',trak); // moov/trak/mdia/mdhd
createMdhdBox(mdia); // moov/trak/mdia/hdlr
createHdlrBox(mdia); // moov/trak/mdia/minf
var minf=ISOBoxer.createBox('minf',mdia);switch(adaptationSet.type){case constants.VIDEO: // moov/trak/mdia/minf/vmhd
createVmhdBox(minf);break;case constants.AUDIO: // moov/trak/mdia/minf/smhd
createSmhdBox(minf);break;default:break;} // moov/trak/mdia/minf/dinf
var dinf=ISOBoxer.createBox('dinf',minf); // moov/trak/mdia/minf/dinf/dref
createDrefBox(dinf); // moov/trak/mdia/minf/stbl
var stbl=ISOBoxer.createBox('stbl',minf); // Create empty stts, stsc, stco and stsz boxes
// Use data field as for codem-isoboxer unknown boxes for setting fields value
// moov/trak/mdia/minf/stbl/stts
var stts=ISOBoxer.createFullBox('stts',stbl);stts._data = [0,0,0,0,0,0,0,0]; // version = 0, flags = 0, entry_count = 0
// moov/trak/mdia/minf/stbl/stsc
var stsc=ISOBoxer.createFullBox('stsc',stbl);stsc._data = [0,0,0,0,0,0,0,0]; // version = 0, flags = 0, entry_count = 0
// moov/trak/mdia/minf/stbl/stco
var stco=ISOBoxer.createFullBox('stco',stbl);stco._data = [0,0,0,0,0,0,0,0]; // version = 0, flags = 0, entry_count = 0
// moov/trak/mdia/minf/stbl/stsz
var stsz=ISOBoxer.createFullBox('stsz',stbl);stsz._data = [0,0,0,0,0,0,0,0,0,0,0,0]; // version = 0, flags = 0, sample_size = 0, sample_count = 0
// moov/trak/mdia/minf/stbl/stsd
createStsdBox(stbl); // moov/mvex
var mvex=ISOBoxer.createBox('mvex',moov); // moov/mvex/trex
createTrexBox(mvex);if(contentProtection && protectionController){var supportedKS=protectionController.getSupportedKeySystemsFromContentProtection(contentProtection);createProtectionSystemSpecificHeaderBox(moov,supportedKS);}}function createMvhdBox(moov){var mvhd=ISOBoxer.createFullBox('mvhd',moov);mvhd.version = 1; // version = 1 in order to have 64bits duration value
mvhd.creation_time = 0; // the creation time of the presentation => ignore (set to 0)
mvhd.modification_time = 0; // the most recent time the presentation was modified => ignore (set to 0)
mvhd.timescale = timescale; // the time-scale for the entire presentation => 10000000 for MSS
mvhd.duration = Math.round(period.duration * timescale); // the length of the presentation (in the indicated timescale) => take duration of period
mvhd.rate = 1.0; // 16.16 number, '1.0' = normal playback
mvhd.volume = 1.0; // 8.8 number, '1.0' = full volume
mvhd.reserved1 = 0;mvhd.reserved2 = [0x0,0x0];mvhd.matrix = [1,0,0, // provides a transformation matrix for the video;
0,1,0, // (u,v,w) are restricted here to (0,0,1)
0,0,16384];mvhd.pre_defined = [0,0,0,0,0,0];mvhd.next_track_ID = trackId + 1; // indicates a value to use for the track ID of the next track to be added to this presentation
return mvhd;}function createTkhdBox(trak){var tkhd=ISOBoxer.createFullBox('tkhd',trak);tkhd.version = 1; // version = 1 in order to have 64bits duration value
tkhd.flags = 0x1 | // Track_enabled (0x000001): Indicates that the track is enabled
0x2 | // Track_in_movie (0x000002): Indicates that the track is used in the presentation
0x4; // Track_in_preview (0x000004): Indicates that the track is used when previewing the presentation
tkhd.creation_time = 0; // the creation time of the presentation => ignore (set to 0)
tkhd.modification_time = 0; // the most recent time the presentation was modified => ignore (set to 0)
tkhd.track_ID = trackId; // uniquely identifies this track over the entire life-time of this presentation
tkhd.reserved1 = 0;tkhd.duration = Math.round(period.duration * timescale); // the duration of this track (in the timescale indicated in the Movie Header Box) => take duration of period
tkhd.reserved2 = [0x0,0x0];tkhd.layer = 0; // specifies the front-to-back ordering of video tracks; tracks with lower numbers are closer to the viewer => 0 since only one video track
tkhd.alternate_group = 0; // specifies a group or collection of tracks => ignore
tkhd.volume = 1.0; // '1.0' = full volume
tkhd.reserved3 = 0;tkhd.matrix = [1,0,0, // provides a transformation matrix for the video;
0,1,0, // (u,v,w) are restricted here to (0,0,1)
0,0,16384];tkhd.width = representation.width; // visual presentation width
tkhd.height = representation.height; // visual presentation height
return tkhd;}function createMdhdBox(mdia){var mdhd=ISOBoxer.createFullBox('mdhd',mdia);mdhd.version = 1; // version = 1 in order to have 64bits duration value
mdhd.creation_time = 0; // the creation time of the presentation => ignore (set to 0)
mdhd.modification_time = 0; // the most recent time the presentation was modified => ignore (set to 0)
mdhd.timescale = timescale; // the time-scale for the entire presentation
mdhd.duration = Math.round(period.duration * timescale); // the duration of this media (in the scale of the timescale). If the duration cannot be determined then duration is set to all 1s.
mdhd.language = adaptationSet.lang || 'und'; // declares the language code for this media (see getLanguageCode())
mdhd.pre_defined = 0;return mdhd;}function createHdlrBox(mdia){var hdlr=ISOBoxer.createFullBox('hdlr',mdia);hdlr.pre_defined = 0;switch(adaptationSet.type){case constants.VIDEO:hdlr.handler_type = 'vide';break;case constants.AUDIO:hdlr.handler_type = 'soun';break;default:hdlr.handler_type = 'meta';break;}hdlr.name = representation.id;hdlr.reserved = [0,0,0];return hdlr;}function createVmhdBox(minf){var vmhd=ISOBoxer.createFullBox('vmhd',minf);vmhd.flags = 1;vmhd.graphicsmode = 0; // specifies a composition mode for this video track, from the following enumerated set, which may be extended by derived specifications: copy = 0 copy over the existing image
vmhd.opcolor = [0,0,0]; // is a set of 3 colour values (red, green, blue) available for use by graphics modes
return vmhd;}function createSmhdBox(minf){var smhd=ISOBoxer.createFullBox('smhd',minf);smhd.flags = 1;smhd.balance = 0; // is a fixed-point 8.8 number that places mono audio tracks in a stereo space; 0 is centre (the normal value); full left is -1.0 and full right is 1.0.
smhd.reserved = 0;return smhd;}function createDrefBox(dinf){var dref=ISOBoxer.createFullBox('dref',dinf);dref.entry_count = 1;dref.entries = [];var url=ISOBoxer.createFullBox('url ',dref,false);url.location = '';url.flags = 1;dref.entries.push(url);return dref;}function createStsdBox(stbl){var stsd=ISOBoxer.createFullBox('stsd',stbl);stsd.entries = [];switch(adaptationSet.type){case constants.VIDEO:case constants.AUDIO:stsd.entries.push(createSampleEntry(stsd));break;default:break;}stsd.entry_count = stsd.entries.length; // is an integer that counts the actual entries
return stsd;}function createSampleEntry(stsd){var codec=representation.codecs.substring(0,representation.codecs.indexOf('.'));switch(codec){case 'avc1':return createAVCVisualSampleEntry(stsd,codec);case 'mp4a':return createMP4AudioSampleEntry(stsd,codec);default:throw {name:'Unsupported codec',message:'Unsupported codec',data:{codec:codec}};}}function createAVCVisualSampleEntry(stsd,codec){var avc1=undefined;if(contentProtection){avc1 = ISOBoxer.createBox('encv',stsd,false);}else {avc1 = ISOBoxer.createBox('avc1',stsd,false);} // SampleEntry fields
avc1.reserved1 = [0x0,0x0,0x0,0x0,0x0,0x0];avc1.data_reference_index = 1; // VisualSampleEntry fields
avc1.pre_defined1 = 0;avc1.reserved2 = 0;avc1.pre_defined2 = [0,0,0];avc1.height = representation.height;avc1.width = representation.width;avc1.horizresolution = 72; // 72 dpi
avc1.vertresolution = 72; // 72 dpi
avc1.reserved3 = 0;avc1.frame_count = 1; // 1 compressed video frame per sample
avc1.compressorname = [0x0A,0x41,0x56,0x43,0x20,0x43,0x6F,0x64, // = 'AVC Coding';
0x69,0x6E,0x67,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00];avc1.depth = 0x0018; // 0x0018 – images are in colour with no alpha.
avc1.pre_defined3 = 65535;avc1.config = createAVC1ConfigurationRecord();if(contentProtection){ // Create and add Protection Scheme Info Box
var sinf=ISOBoxer.createBox('sinf',avc1); // Create and add Original Format Box => indicate codec type of the encrypted content
createOriginalFormatBox(sinf,codec); // Create and add Scheme Type box
createSchemeTypeBox(sinf); // Create and add Scheme Information Box
createSchemeInformationBox(sinf);}return avc1;}function createAVC1ConfigurationRecord(){var avcC=null;var avcCLength=15; // length = 15 by default (0 SPS and 0 PPS)
// First get all SPS and PPS from codecPrivateData
var sps=[];var pps=[];var AVCProfileIndication=0;var AVCLevelIndication=0;var profile_compatibility=0;var nalus=representation.codecPrivateData.split('00000001').slice(1);var naluBytes=undefined,naluType=undefined;for(var _i=0;_i < nalus.length;_i++) {naluBytes = hexStringtoBuffer(nalus[_i]);naluType = naluBytes[0] & 0x1F;switch(naluType){case NALUTYPE_SPS:sps.push(naluBytes);avcCLength += naluBytes.length + 2; // 2 = sequenceParameterSetLength field length
break;case NALUTYPE_PPS:pps.push(naluBytes);avcCLength += naluBytes.length + 2; // 2 = pictureParameterSetLength field length
break;default:break;}} // Get profile and level from SPS
if(sps.length > 0){AVCProfileIndication = sps[0][1];profile_compatibility = sps[0][2];AVCLevelIndication = sps[0][3];} // Generate avcC buffer
avcC = new Uint8Array(avcCLength);var i=0; // length
avcC[i++] = (avcCLength & 0xFF000000) >> 24;avcC[i++] = (avcCLength & 0x00FF0000) >> 16;avcC[i++] = (avcCLength & 0x0000FF00) >> 8;avcC[i++] = avcCLength & 0x000000FF;avcC.set([0x61,0x76,0x63,0x43],i); // type = 'avcC'
i += 4;avcC[i++] = 1; // configurationVersion = 1
avcC[i++] = AVCProfileIndication;avcC[i++] = profile_compatibility;avcC[i++] = AVCLevelIndication;avcC[i++] = 0xFF; // '11111' + lengthSizeMinusOne = 3
avcC[i++] = 0xE0 | sps.length; // '111' + numOfSequenceParameterSets
for(var n=0;n < sps.length;n++) {avcC[i++] = (sps[n].length & 0xFF00) >> 8;avcC[i++] = sps[n].length & 0x00FF;avcC.set(sps[n],i);i += sps[n].length;}avcC[i++] = pps.length; // numOfPictureParameterSets
for(var n=0;n < pps.length;n++) {avcC[i++] = (pps[n].length & 0xFF00) >> 8;avcC[i++] = pps[n].length & 0x00FF;avcC.set(pps[n],i);i += pps[n].length;}return avcC;}function createMP4AudioSampleEntry(stsd,codec){var mp4a=undefined;if(contentProtection){mp4a = ISOBoxer.createBox('enca',stsd,false);}else {mp4a = ISOBoxer.createBox('mp4a',stsd,false);} // SampleEntry fields
mp4a.reserved1 = [0x0,0x0,0x0,0x0,0x0,0x0];mp4a.data_reference_index = 1; // AudioSampleEntry fields
mp4a.reserved2 = [0x0,0x0];mp4a.channelcount = representation.audioChannels;mp4a.samplesize = 16;mp4a.pre_defined = 0;mp4a.reserved_3 = 0;mp4a.samplerate = representation.audioSamplingRate << 16;mp4a.esds = createMPEG4AACESDescriptor();if(contentProtection){ // Create and add Protection Scheme Info Box
var sinf=ISOBoxer.createBox('sinf',mp4a); // Create and add Original Format Box => indicate codec type of the encrypted content
createOriginalFormatBox(sinf,codec); // Create and add Scheme Type box
createSchemeTypeBox(sinf); // Create and add Scheme Information Box
createSchemeInformationBox(sinf);}return mp4a;}function createMPEG4AACESDescriptor(){ // AudioSpecificConfig (see ISO/IEC 14496-3, subpart 1) => corresponds to hex bytes contained in 'codecPrivateData' field
var audioSpecificConfig=hexStringtoBuffer(representation.codecPrivateData); // ESDS length = esds box header length (= 12) +
// ES_Descriptor header length (= 5) +
// DecoderConfigDescriptor header length (= 15) +
// decoderSpecificInfo header length (= 2) +
// AudioSpecificConfig length (= codecPrivateData length)
var esdsLength=34 + audioSpecificConfig.length;var esds=new Uint8Array(esdsLength);var i=0; // esds box
esds[i++] = (esdsLength & 0xFF000000) >> 24; // esds box length
esds[i++] = (esdsLength & 0x00FF0000) >> 16; // ''
esds[i++] = (esdsLength & 0x0000FF00) >> 8; // ''
esds[i++] = esdsLength & 0x000000FF; // ''
esds.set([0x65,0x73,0x64,0x73],i); // type = 'esds'
i += 4;esds.set([0,0,0,0],i); // version = 0, flags = 0
i += 4; // ES_Descriptor (see ISO/IEC 14496-1 (Systems))
esds[i++] = 0x03; // tag = 0x03 (ES_DescrTag)
esds[i++] = 20 + audioSpecificConfig.length; // size
esds[i++] = (trackId & 0xFF00) >> 8; // ES_ID = track_id
esds[i++] = trackId & 0x00FF; // ''
esds[i++] = 0; // flags and streamPriority
// DecoderConfigDescriptor (see ISO/IEC 14496-1 (Systems))
esds[i++] = 0x04; // tag = 0x04 (DecoderConfigDescrTag)
esds[i++] = 15 + audioSpecificConfig.length; // size
esds[i++] = 0x40; // objectTypeIndication = 0x40 (MPEG-4 AAC)
esds[i] = 0x05 << 2; // streamType = 0x05 (Audiostream)
esds[i] |= 0 << 1; // upStream = 0
esds[i++] |= 1; // reserved = 1
esds[i++] = 0xFF; // buffersizeDB = undefined
esds[i++] = 0xFF; // ''
esds[i++] = 0xFF; // ''
esds[i++] = (representation.bandwidth & 0xFF000000) >> 24; // maxBitrate
esds[i++] = (representation.bandwidth & 0x00FF0000) >> 16; // ''
esds[i++] = (representation.bandwidth & 0x0000FF00) >> 8; // ''
esds[i++] = representation.bandwidth & 0x000000FF; // ''
esds[i++] = (representation.bandwidth & 0xFF000000) >> 24; // avgbitrate
esds[i++] = (representation.bandwidth & 0x00FF0000) >> 16; // ''
esds[i++] = (representation.bandwidth & 0x0000FF00) >> 8; // ''
esds[i++] = representation.bandwidth & 0x000000FF; // ''
// DecoderSpecificInfo (see ISO/IEC 14496-1 (Systems))
esds[i++] = 0x05; // tag = 0x05 (DecSpecificInfoTag)
esds[i++] = audioSpecificConfig.length; // size
esds.set(audioSpecificConfig,i); // AudioSpecificConfig bytes
return esds;}function createOriginalFormatBox(sinf,codec){var frma=ISOBoxer.createBox('frma',sinf);frma.data_format = stringToCharCode(codec);}function createSchemeTypeBox(sinf){var schm=ISOBoxer.createFullBox('schm',sinf);schm.flags = 0;schm.version = 0;schm.scheme_type = 0x63656E63; // 'cenc' => common encryption
schm.scheme_version = 0x00010000; // version set to 0x00010000 (Major version 1, Minor version 0)
}function createSchemeInformationBox(sinf){var schi=ISOBoxer.createBox('schi',sinf); // Create and add Track Encryption Box
createTrackEncryptionBox(schi);}function createProtectionSystemSpecificHeaderBox(moov,keySystems){var pssh_bytes=undefined;var pssh=undefined;var i=undefined;var parsedBuffer=undefined;for(i = 0;i < keySystems.length;i += 1) {pssh_bytes = keySystems[i].initData;parsedBuffer = ISOBoxer.parseBuffer(pssh_bytes);pssh = parsedBuffer.fetch('pssh');if(pssh){ISOBoxer.Utils.appendBox(moov,pssh);}}}function createTrackEncryptionBox(schi){var tenc=ISOBoxer.createFullBox('tenc',schi);tenc.flags = 0;tenc.version = 0;tenc.default_IsEncrypted = 0x1;tenc.default_IV_size = 8;tenc.default_KID = contentProtection && contentProtection.length > 0 && contentProtection[0]['cenc:default_KID']?contentProtection[0]['cenc:default_KID']:[0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0];}function createTrexBox(moov){var trex=ISOBoxer.createFullBox('trex',moov);trex.track_ID = trackId;trex.default_sample_description_index = 1;trex.default_sample_duration = 0;trex.default_sample_size = 0;trex.default_sample_flags = 0;return trex;}function hexStringtoBuffer(str){var buf=new Uint8Array(str.length / 2);var i=undefined;for(i = 0;i < str.length / 2;i += 1) {buf[i] = parseInt('' + str[i * 2] + str[i * 2 + 1],16);}return buf;}function stringToCharCode(str){var code=0;var i=undefined;for(i = 0;i < str.length;i += 1) {code |= str.charCodeAt(i) << (str.length - i - 1) * 8;}return code;}function generateMoov(rep){if(!rep || !rep.adaptation){return;}var isoFile=undefined,arrayBuffer=undefined;representation = rep;adaptationSet = representation.adaptation;period = adaptationSet.period;trackId = adaptationSet.index + 1;contentProtection = period.mpd.manifest.Period_asArray[period.index].AdaptationSet_asArray[adaptationSet.index].ContentProtection;timescale = period.mpd.manifest.Period_asArray[period.index].AdaptationSet_asArray[adaptationSet.index].SegmentTemplate.timescale;isoFile = ISOBoxer.createFile();createFtypBox(isoFile);createMoovBox(isoFile);arrayBuffer = isoFile.write();return arrayBuffer;}instance = {generateMoov:generateMoov};return instance;}MssFragmentMoovProcessor.__dashjs_factory_name = 'MssFragmentMoovProcessor';exports['default'] = dashjs.FactoryMaker.getClassFactory(MssFragmentMoovProcessor); /* jshint ignore:line */module.exports = exports['default'];
//# sourceMappingURL=MssFragmentMoovProcessor.js.map