UNPKG

dashjs

Version:

A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.

145 lines (144 loc) 25.3 kB
'use strict';Object.defineProperty(exports,"__esModule",{value:true});/** * 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 MssParser * @ignore * @param {Object} config object */function MssParser(config){config=config||{};var BASE64=config.BASE64;var debug=config.debug;var constants=config.constants;var manifestModel=config.manifestModel;var DEFAULT_TIME_SCALE=10000000.0;var SUPPORTED_CODECS=['AAC','AACL','AVC1','H264','TTML','DFXP'];// MPEG-DASH Role and accessibility mapping for text tracks according to ETSI TS 103 285 v1.1.1 (section 7.1.2) var ROLE={'CAPT':'main','SUBT':'alternate','DESC':'main'};var ACCESSIBILITY={'DESC':'2'};var samplingFrequencyIndex={96000:0x0,88200:0x1,64000:0x2,48000:0x3,44100:0x4,32000:0x5,24000:0x6,22050:0x7,16000:0x8,12000:0x9,11025:0xA,8000:0xB,7350:0xC};var mimeTypeMap={'video':'video/mp4','audio':'audio/mp4','text':'application/mp4'};var instance=void 0,logger=void 0,mediaPlayerModel=void 0;function setup(){logger=debug.getLogger(instance);mediaPlayerModel=config.mediaPlayerModel;}function mapPeriod(smoothStreamingMedia,timescale){var period={};var streams=void 0,adaptation=void 0;// For each StreamIndex node, create an AdaptationSet element period.AdaptationSet_asArray=[];streams=smoothStreamingMedia.getElementsByTagName('StreamIndex');for(var i=0;i<streams.length;i++){adaptation=mapAdaptationSet(streams[i],timescale);if(adaptation!==null){period.AdaptationSet_asArray.push(adaptation);}}if(period.AdaptationSet_asArray.length>0){period.AdaptationSet=period.AdaptationSet_asArray.length>1?period.AdaptationSet_asArray:period.AdaptationSet_asArray[0];}return period;}function mapAdaptationSet(streamIndex,timescale){var adaptationSet={};var representations=[];var segmentTemplate=void 0;var qualityLevels=void 0,representation=void 0,segments=void 0,i=void 0;adaptationSet.id=streamIndex.getAttribute('Name')?streamIndex.getAttribute('Name'):streamIndex.getAttribute('Type');adaptationSet.contentType=streamIndex.getAttribute('Type');adaptationSet.lang=streamIndex.getAttribute('Language')||'und';adaptationSet.mimeType=mimeTypeMap[adaptationSet.contentType];adaptationSet.subType=streamIndex.getAttribute('Subtype');adaptationSet.maxWidth=streamIndex.getAttribute('MaxWidth');adaptationSet.maxHeight=streamIndex.getAttribute('MaxHeight');// Map text tracks subTypes to MPEG-DASH AdaptationSet role and accessibility (see ETSI TS 103 285 v1.1.1, section 7.1.2) if(adaptationSet.subType){if(ROLE[adaptationSet.subType]){var role={schemeIdUri:'urn:mpeg:dash:role:2011',value:ROLE[adaptationSet.subType]};adaptationSet.Role=role;adaptationSet.Role_asArray=[role];}if(ACCESSIBILITY[adaptationSet.subType]){var accessibility={schemeIdUri:'urn:tva:metadata:cs:AudioPurposeCS:2007',value:ACCESSIBILITY[adaptationSet.subType]};adaptationSet.Accessibility=accessibility;adaptationSet.Accessibility_asArray=[accessibility];}}// Create a SegmentTemplate with a SegmentTimeline segmentTemplate=mapSegmentTemplate(streamIndex,timescale);qualityLevels=streamIndex.getElementsByTagName('QualityLevel');// For each QualityLevel node, create a Representation element for(i=0;i<qualityLevels.length;i++){// Propagate BaseURL and mimeType qualityLevels[i].BaseURL=adaptationSet.BaseURL;qualityLevels[i].mimeType=adaptationSet.mimeType;// Set quality level id qualityLevels[i].Id=adaptationSet.id+'_'+qualityLevels[i].getAttribute('Index');// Map Representation to QualityLevel representation=mapRepresentation(qualityLevels[i],streamIndex);if(representation!==null){// Copy SegmentTemplate into Representation representation.SegmentTemplate=segmentTemplate;representations.push(representation);}}if(representations.length===0){return null;}adaptationSet.Representation=representations.length>1?representations:representations[0];adaptationSet.Representation_asArray=representations;// Set SegmentTemplate adaptationSet.SegmentTemplate=segmentTemplate;segments=segmentTemplate.SegmentTimeline.S_asArray;return adaptationSet;}function mapRepresentation(qualityLevel,streamIndex){var representation={};var type=streamIndex.getAttribute('Type');var fourCCValue=null;representation.id=qualityLevel.Id;representation.bandwidth=parseInt(qualityLevel.getAttribute('Bitrate'),10);representation.mimeType=qualityLevel.mimeType;representation.width=parseInt(qualityLevel.getAttribute('MaxWidth'),10);representation.height=parseInt(qualityLevel.getAttribute('MaxHeight'),10);fourCCValue=qualityLevel.getAttribute('FourCC');// If FourCC not defined at QualityLevel level, then get it from StreamIndex level if(fourCCValue===null||fourCCValue===''){fourCCValue=streamIndex.getAttribute('FourCC');}// If still not defined (optionnal for audio stream, see https://msdn.microsoft.com/en-us/library/ff728116%28v=vs.95%29.aspx), // then we consider the stream is an audio AAC stream if(fourCCValue===null||fourCCValue===''){if(type==='audio'){fourCCValue='AAC';}else if(type==='video'){logger.debug('FourCC is not defined whereas it is required for a QualityLevel element for a StreamIndex of type "video"');return null;}}// Check if codec is supported if(SUPPORTED_CODECS.indexOf(fourCCValue.toUpperCase())===-1){// Do not send warning logger.warn('Codec not supported: '+fourCCValue);return null;}// Get codecs value according to FourCC field if(fourCCValue==='H264'||fourCCValue==='AVC1'){representation.codecs=getH264Codec(qualityLevel);}else if(fourCCValue.indexOf('AAC')>=0){representation.codecs=getAACCodec(qualityLevel,fourCCValue);representation.audioSamplingRate=parseInt(qualityLevel.getAttribute('SamplingRate'),10);representation.audioChannels=parseInt(qualityLevel.getAttribute('Channels'),10);}else if(fourCCValue.indexOf('TTML')||fourCCValue.indexOf('DFXP')){representation.codecs=constants.STPP;}representation.codecPrivateData=''+qualityLevel.getAttribute('CodecPrivateData');representation.BaseURL=qualityLevel.BaseURL;return representation;}function getH264Codec(qualityLevel){var codecPrivateData=qualityLevel.getAttribute('CodecPrivateData').toString();var nalHeader=void 0,avcoti=void 0;// Extract from the CodecPrivateData field the hexadecimal representation of the following // three bytes in the sequence parameter set NAL unit. // => Find the SPS nal header nalHeader=/00000001[0-9]7/.exec(codecPrivateData);// => Find the 6 characters after the SPS nalHeader (if it exists) avcoti=nalHeader&&nalHeader[0]?codecPrivateData.substr(codecPrivateData.indexOf(nalHeader[0])+10,6):undefined;return'avc1.'+avcoti;}function getAACCodec(qualityLevel,fourCCValue){var samplingRate=parseInt(qualityLevel.getAttribute('SamplingRate'),10);var codecPrivateData=qualityLevel.getAttribute('CodecPrivateData').toString();var objectType=0;var codecPrivateDataHex=void 0,arr16=void 0,indexFreq=void 0,extensionSamplingFrequencyIndex=void 0;//chrome problem, in implicit AAC HE definition, so when AACH is detected in FourCC //set objectType to 5 => strange, it should be 2 if(fourCCValue==='AACH'){objectType=0x05;}//if codecPrivateData is empty, build it : if(codecPrivateData===undefined||codecPrivateData===''){objectType=0x02;//AAC Main Low Complexity => object Type = 2 indexFreq=samplingFrequencyIndex[samplingRate];if(fourCCValue==='AACH'){// 4 bytes : XXXXX XXXX XXXX XXXX XXXXX XXX XXXXXXX // ' ObjectType' 'Freq Index' 'Channels value' 'Extens Sampl Freq' 'ObjectType' 'GAS' 'alignment = 0' objectType=0x05;// High Efficiency AAC Profile = object Type = 5 SBR codecPrivateData=new Uint8Array(4);extensionSamplingFrequencyIndex=samplingFrequencyIndex[samplingRate*2];// in HE AAC Extension Sampling frequence // equals to SamplingRate*2 //Freq Index is present for 3 bits in the first byte, last bit is in the second codecPrivateData[0]=objectType<<3|indexFreq>>1;codecPrivateData[1]=indexFreq<<7|qualityLevel.Channels<<3|extensionSamplingFrequencyIndex>>1;codecPrivateData[2]=extensionSamplingFrequencyIndex<<7|0x02<<2;// origin object type equals to 2 => AAC Main Low Complexity codecPrivateData[3]=0x0;//alignment bits arr16=new Uint16Array(2);arr16[0]=(codecPrivateData[0]<<8)+codecPrivateData[1];arr16[1]=(codecPrivateData[2]<<8)+codecPrivateData[3];//convert decimal to hex value codecPrivateDataHex=arr16[0].toString(16);codecPrivateDataHex=arr16[0].toString(16)+arr16[1].toString(16);}else{// 2 bytes : XXXXX XXXX XXXX XXX // ' ObjectType' 'Freq Index' 'Channels value' 'GAS = 000' codecPrivateData=new Uint8Array(2);//Freq Index is present for 3 bits in the first byte, last bit is in the second codecPrivateData[0]=objectType<<3|indexFreq>>1;codecPrivateData[1]=indexFreq<<7|parseInt(qualityLevel.getAttribute('Channels'),10)<<3;// put the 2 bytes in an 16 bits array arr16=new Uint16Array(1);arr16[0]=(codecPrivateData[0]<<8)+codecPrivateData[1];//convert decimal to hex value codecPrivateDataHex=arr16[0].toString(16);}codecPrivateData=''+codecPrivateDataHex;codecPrivateData=codecPrivateData.toUpperCase();qualityLevel.setAttribute('CodecPrivateData',codecPrivateData);}else if(objectType===0){objectType=(parseInt(codecPrivateData.substr(0,2),16)&0xF8)>>3;}return'mp4a.40.'+objectType;}function mapSegmentTemplate(streamIndex,timescale){var segmentTemplate={};var mediaUrl=void 0,streamIndexTimeScale=void 0,url=void 0;url=streamIndex.getAttribute('Url');mediaUrl=url?url.replace('{bitrate}','$Bandwidth$'):null;mediaUrl=mediaUrl?mediaUrl.replace('{start time}','$Time$'):null;streamIndexTimeScale=streamIndex.getAttribute('TimeScale');streamIndexTimeScale=streamIndexTimeScale?parseFloat(streamIndexTimeScale):timescale;segmentTemplate.media=mediaUrl;segmentTemplate.timescale=streamIndexTimeScale;segmentTemplate.SegmentTimeline=mapSegmentTimeline(streamIndex,segmentTemplate.timescale);return segmentTemplate;}function mapSegmentTimeline(streamIndex,timescale){var segmentTimeline={};var chunks=streamIndex.getElementsByTagName('c');var segments=[];var segment=void 0,prevSegment=void 0,tManifest=void 0,i=void 0,j=void 0,r=void 0;var duration=0;for(i=0;i<chunks.length;i++){segment={};// Get time 't' attribute value tManifest=chunks[i].getAttribute('t');// => segment.tManifest = original timestamp value as a string (for constructing the fragment request url, see DashHandler) // => segment.t = number value of timestamp (maybe rounded value, but only for 0.1 microsecond) segment.tManifest=parseFloat(tManifest);segment.t=parseFloat(tManifest);// Get duration 'd' attribute value segment.d=parseFloat(chunks[i].getAttribute('d'));// If 't' not defined for first segment then t=0 if(i===0&&!segment.t){segment.t=0;}if(i>0){prevSegment=segments[segments.length-1];// Update previous segment duration if not defined if(!prevSegment.d){if(prevSegment.tManifest){prevSegment.d=parseFloat(tManifest)-parseFloat(prevSegment.tManifest);}else{prevSegment.d=segment.t-prevSegment.t;}duration+=prevSegment.d;}// Set segment absolute timestamp if not set in manifest if(!segment.t){if(prevSegment.tManifest){segment.tManifest=parseFloat(prevSegment.tManifest)+prevSegment.d;segment.t=parseFloat(segment.tManifest);}else{segment.t=prevSegment.t+prevSegment.d;}}}if(segment.d){duration+=segment.d;}// Create new segment segments.push(segment);// Support for 'r' attribute (i.e. "repeat" as in MPEG-DASH) r=parseFloat(chunks[i].getAttribute('r'));if(r){for(j=0;j<r-1;j++){prevSegment=segments[segments.length-1];segment={};segment.t=prevSegment.t+prevSegment.d;segment.d=prevSegment.d;if(prevSegment.tManifest){segment.tManifest=parseFloat(prevSegment.tManifest)+prevSegment.d;}duration+=segment.d;segments.push(segment);}}}segmentTimeline.S=segments;segmentTimeline.S_asArray=segments;segmentTimeline.duration=duration/timescale;return segmentTimeline;}function getKIDFromProtectionHeader(protectionHeader){var prHeader=void 0,wrmHeader=void 0,xmlReader=void 0,KID=void 0;// Get PlayReady header as byte array (base64 decoded) prHeader=BASE64.decodeArray(protectionHeader.firstChild.data);// Get Right Management header (WRMHEADER) from PlayReady header wrmHeader=getWRMHeaderFromPRHeader(prHeader);if(wrmHeader){// Convert from multi-byte to unicode wrmHeader=new Uint16Array(wrmHeader.buffer);// Convert to string wrmHeader=String.fromCharCode.apply(null,wrmHeader);// Parse <WRMHeader> to get KID field value xmlReader=new DOMParser().parseFromString(wrmHeader,'application/xml');KID=xmlReader.querySelector('KID').textContent;// Get KID (base64 decoded) as byte array KID=BASE64.decodeArray(KID);// Convert UUID from little-endian to big-endian convertUuidEndianness(KID);}return KID;}function getWRMHeaderFromPRHeader(prHeader){var length=void 0,recordCount=void 0,recordType=void 0,recordLength=void 0,recordValue=void 0;var i=0;// Parse PlayReady header // Length - 32 bits (LE format) length=(prHeader[i+3]<<24)+(prHeader[i+2]<<16)+(prHeader[i+1]<<8)+prHeader[i];i+=4;// Record count - 16 bits (LE format) recordCount=(prHeader[i+1]<<8)+prHeader[i];i+=2;// Parse records while(i<prHeader.length){// Record type - 16 bits (LE format) recordType=(prHeader[i+1]<<8)+prHeader[i];i+=2;// Check if Rights Management header (record type = 0x01) if(recordType===0x01){// Record length - 16 bits (LE format) recordLength=(prHeader[i+1]<<8)+prHeader[i];i+=2;// Record value => contains <WRMHEADER> recordValue=new Uint8Array(recordLength);recordValue.set(prHeader.subarray(i,i+recordLength));return recordValue;}}return null;}function convertUuidEndianness(uuid){swapBytes(uuid,0,3);swapBytes(uuid,1,2);swapBytes(uuid,4,5);swapBytes(uuid,6,7);}function swapBytes(bytes,pos1,pos2){var temp=bytes[pos1];bytes[pos1]=bytes[pos2];bytes[pos2]=temp;}function createPRContentProtection(protectionHeader){var pro={__text:protectionHeader.firstChild.data,__prefix:'mspr'};return{schemeIdUri:'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95',value:'com.microsoft.playready',pro:pro,pro_asArray:pro};}function createWidevineContentProtection(KID){var widevineCP={schemeIdUri:'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',value:'com.widevine.alpha'};if(!KID)return widevineCP;// Create Widevine CENC header (Protocol Buffer) with KID value var wvCencHeader=new Uint8Array(2+KID.length);wvCencHeader[0]=0x12;wvCencHeader[1]=0x10;wvCencHeader.set(KID,2);// Create a pssh box var length=12/* box length, type, version and flags */+16/* SystemID */+4/* data length */+wvCencHeader.length;var pssh=new Uint8Array(length);var i=0;// Set box length value pssh[i++]=(length&0xFF000000)>>24;pssh[i++]=(length&0x00FF0000)>>16;pssh[i++]=(length&0x0000FF00)>>8;pssh[i++]=length&0x000000FF;// Set type ('pssh'), version (0) and flags (0) pssh.set([0x70,0x73,0x73,0x68,0x00,0x00,0x00,0x00],i);i+=8;// Set SystemID ('edef8ba9-79d6-4ace-a3c8-27dcd51d21ed') pssh.set([0xed,0xef,0x8b,0xa9,0x79,0xd6,0x4a,0xce,0xa3,0xc8,0x27,0xdc,0xd5,0x1d,0x21,0xed],i);i+=16;// Set data length value pssh[i++]=(wvCencHeader.length&0xFF000000)>>24;pssh[i++]=(wvCencHeader.length&0x00FF0000)>>16;pssh[i++]=(wvCencHeader.length&0x0000FF00)>>8;pssh[i++]=wvCencHeader.length&0x000000FF;// Copy Widevine CENC header pssh.set(wvCencHeader,i);// Convert to BASE64 string pssh=String.fromCharCode.apply(null,pssh);pssh=BASE64.encodeASCII(pssh);widevineCP.pssh={__text:pssh};return widevineCP;}function processManifest(xmlDoc,manifestLoadedTime){var manifest={};var contentProtections=[];var smoothStreamingMedia=xmlDoc.getElementsByTagName('SmoothStreamingMedia')[0];var protection=xmlDoc.getElementsByTagName('Protection')[0];var protectionHeader=null;var period=void 0,adaptations=void 0,contentProtection=void 0,KID=void 0,timestampOffset=void 0,startTime=void 0,segments=void 0,timescale=void 0,i=void 0,j=void 0;// Set manifest node properties manifest.protocol='MSS';manifest.profiles='urn:mpeg:dash:profile:isoff-live:2011';manifest.type=smoothStreamingMedia.getAttribute('IsLive')==='TRUE'?'dynamic':'static';timescale=smoothStreamingMedia.getAttribute('TimeScale');manifest.timescale=timescale?parseFloat(timescale):DEFAULT_TIME_SCALE;var dvrWindowLength=parseFloat(smoothStreamingMedia.getAttribute('DVRWindowLength'));if(dvrWindowLength===0&&smoothStreamingMedia.getAttribute('CanSeek')==='TRUE'){dvrWindowLength=Infinity;}if(dvrWindowLength>0){manifest.timeShiftBufferDepth=dvrWindowLength/manifest.timescale;}var duration=parseFloat(smoothStreamingMedia.getAttribute('Duration'));manifest.mediaPresentationDuration=duration===0?Infinity:duration/manifest.timescale;// By default, set minBufferTime to 2 sec. (but set below according to video segment duration) manifest.minBufferTime=2;manifest.ttmlTimeIsRelative=true;// Live manifest with Duration = start-over if(manifest.type==='dynamic'&&duration>0){manifest.type='static';// We set timeShiftBufferDepth to initial duration, to be used by MssFragmentController to update segment timeline manifest.timeShiftBufferDepth=duration/manifest.timescale;// Duration will be set according to current segment timeline duration (see below) }if(manifest.type==='dynamic'&&manifest.timeShiftBufferDepth<Infinity){manifest.refreshManifestOnSwitchTrack=true;// Refresh manifest when switching tracks manifest.doNotUpdateDVRWindowOnBufferUpdated=true;// DVRWindow is update by MssFragmentMoofPocessor based on tfrf boxes manifest.ignorePostponeTimePeriod=true;// Never update manifest }// Map period node to manifest root node manifest.Period=mapPeriod(smoothStreamingMedia,manifest.timescale);manifest.Period_asArray=[manifest.Period];// Initialize period start time period=manifest.Period;period.start=0;// Uncomment to test live to static manifests // if (manifest.type !== 'static') { // manifest.type = 'static'; // manifest.mediaPresentationDuration = manifest.timeShiftBufferDepth; // manifest.timeShiftBufferDepth = null; // } // ContentProtection node if(protection!==undefined){protectionHeader=xmlDoc.getElementsByTagName('ProtectionHeader')[0];// Some packagers put newlines into the ProtectionHeader base64 string, which is not good // because this cannot be correctly parsed. Let's just filter out any newlines found in there. protectionHeader.firstChild.data=protectionHeader.firstChild.data.replace(/\n|\r/g,'');// Get KID (in CENC format) from protection header KID=getKIDFromProtectionHeader(protectionHeader);// Create ContentProtection for PlayReady contentProtection=createPRContentProtection(protectionHeader);contentProtection['cenc:default_KID']=KID;contentProtections.push(contentProtection);// Create ContentProtection for Widevine (as a CENC protection) contentProtection=createWidevineContentProtection(KID);contentProtection['cenc:default_KID']=KID;contentProtections.push(contentProtection);manifest.ContentProtection=contentProtections;manifest.ContentProtection_asArray=contentProtections;}adaptations=period.AdaptationSet_asArray;for(i=0;i<adaptations.length;i+=1){adaptations[i].SegmentTemplate.initialization='$Bandwidth$';// Propagate content protection information into each adaptation if(manifest.ContentProtection!==undefined){adaptations[i].ContentProtection=manifest.ContentProtection;adaptations[i].ContentProtection_asArray=manifest.ContentProtection_asArray;}if(adaptations[i].contentType==='video'){// Set minBufferTime manifest.minBufferTime=adaptations[i].SegmentTemplate.SegmentTimeline.S_asArray[0].d/adaptations[i].SegmentTemplate.timescale*2;if(manifest.type==='dynamic'){// Set availabilityStartTime segments=adaptations[i].SegmentTemplate.SegmentTimeline.S_asArray;var endTime=(segments[segments.length-1].t+segments[segments.length-1].d)/adaptations[i].SegmentTemplate.timescale*1000;manifest.availabilityStartTime=new Date(manifestLoadedTime.getTime()-endTime);// Match timeShiftBufferDepth to video segment timeline duration if(manifest.timeShiftBufferDepth>0&&manifest.timeShiftBufferDepth!==Infinity&&manifest.timeShiftBufferDepth>adaptations[i].SegmentTemplate.SegmentTimeline.duration){manifest.timeShiftBufferDepth=adaptations[i].SegmentTemplate.SegmentTimeline.duration;}}}}if(manifest.timeShiftBufferDepth<manifest.minBufferTime){manifest.minBufferTime=manifest.timeShiftBufferDepth;}// Delete Content Protection under root manifest node delete manifest.ContentProtection;delete manifest.ContentProtection_asArray;// In case of VOD streams, check if start time is greater than 0 // Then determine timestamp offset according to higher audio/video start time // (use case = live stream delinearization) if(manifest.type==='static'){// In case of start-over stream and manifest reloading (due to track switch) // we consider previous timestampOffset to keep timelines synchronized var prevManifest=manifestModel.getValue();if(prevManifest&&prevManifest.timestampOffset){timestampOffset=prevManifest.timestampOffset;}else{for(i=0;i<adaptations.length;i++){if(adaptations[i].contentType==='audio'||adaptations[i].contentType==='video'){segments=adaptations[i].SegmentTemplate.SegmentTimeline.S_asArray;startTime=segments[0].t/adaptations[i].SegmentTemplate.timescale;if(timestampOffset===undefined){timestampOffset=startTime;}timestampOffset=Math.min(timestampOffset,startTime);// Correct content duration according to minimum adaptation's segment timeline duration // in order to force <video> element sending 'ended' event manifest.mediaPresentationDuration=Math.min(manifest.mediaPresentationDuration,adaptations[i].SegmentTemplate.SegmentTimeline.duration);}}}// Patch segment templates timestamps and determine period start time (since audio/video should not be aligned to 0) if(timestampOffset>0){manifest.timestampOffset=timestampOffset;for(i=0;i<adaptations.length;i++){segments=adaptations[i].SegmentTemplate.SegmentTimeline.S_asArray;for(j=0;j<segments.length;j++){if(!segments[j].tManifest){segments[j].tManifest=segments[j].t;}segments[j].t-=timestampOffset*adaptations[i].SegmentTemplate.timescale;}if(adaptations[i].contentType==='audio'||adaptations[i].contentType==='video'){period.start=Math.max(segments[0].t,period.start);adaptations[i].SegmentTemplate.presentationTimeOffset=period.start;}}period.start/=manifest.timescale;}}// Floor the duration to get around precision differences between segments timestamps and MSE buffer timestamps // and then avoid 'ended' event not being raised manifest.mediaPresentationDuration=Math.floor(manifest.mediaPresentationDuration*1000)/1000;period.duration=manifest.mediaPresentationDuration;return manifest;}function parseDOM(data){var xmlDoc=null;if(window.DOMParser){var parser=new window.DOMParser();xmlDoc=parser.parseFromString(data,'text/xml');if(xmlDoc.getElementsByTagName('parsererror').length>0){throw new Error('parsing the manifest failed');}}return xmlDoc;}function getMatchers(){return null;}function getIron(){return null;}function internalParse(data){var xmlDoc=null;var manifest=null;var startTime=window.performance.now();// Parse the MSS XML manifest xmlDoc=parseDOM(data);var xmlParseTime=window.performance.now();if(xmlDoc===null){return null;}// Convert MSS manifest into DASH manifest manifest=processManifest(xmlDoc,new Date());var mss2dashTime=window.performance.now();logger.info('Parsing complete: (xmlParsing: '+(xmlParseTime-startTime).toPrecision(3)+'ms, mss2dash: '+(mss2dashTime-xmlParseTime).toPrecision(3)+'ms, total: '+((mss2dashTime-startTime)/1000).toPrecision(3)+'s)');return manifest;}instance={parse:internalParse,getMatchers:getMatchers,getIron:getIron};setup();return instance;}MssParser.__dashjs_factory_name='MssParser';exports.default=dashjs.FactoryMaker.getClassFactory(MssParser);/* jshint ignore:line */ //# sourceMappingURL=MssParser.js.map