UNPKG

@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
/** * 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