@l5i/dashjs
Version:
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
144 lines (143 loc) • 26.1 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 MssParser
* @param {Object} config object
*/'use strict';Object.defineProperty(exports,'__esModule',{value:true});function MssParser(config){config = config || {};var BASE64=config.BASE64;var debug=config.debug;var constants=config.constants;var DEFAULT_TIME_SCALE=10000000.0;var SUPPORTED_CODECS=['AAC','AACL','AVC1','H264','TTML','DFXP']; // MPEG-DASH Role and accessibility mapping according to ETSI TS 103 285 v1.1.1 (section 7.1.2)
var ROLE={'SUBT':'alternate','CAPT':'alternate', // 'CAPT' is commonly equivalent to 'SUBT'
'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=undefined,logger=undefined,mediaPlayerModel=undefined;function setup(){logger = debug.getLogger(instance);mediaPlayerModel = config.mediaPlayerModel;}function mapPeriod(smoothStreamingMedia,timescale){var period={};var streams=undefined,adaptation=undefined; // 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=undefined;var qualityLevels=undefined,representation=undefined,segments=undefined,i=undefined;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 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=undefined,avcoti=undefined; // 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=undefined,arr16=undefined,indexFreq=undefined,extensionSamplingFrequencyIndex=undefined; //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=undefined,streamIndexTimeScale=undefined;mediaUrl = streamIndex.getAttribute('Url').replace('{bitrate}','$Bandwidth$');mediaUrl = mediaUrl.replace('{start time}','$Time$');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=undefined;var prevSegment=undefined;var tManifest=undefined;var i=undefined,j=undefined,r=undefined;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=undefined,wrmHeader=undefined,xmlReader=undefined,KID=undefined; // 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); // 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=undefined,recordCount=undefined,recordType=undefined,recordLength=undefined,recordValue=undefined;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(protectionHeader,KID){ // 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);return {schemeIdUri:'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',value:'com.widevine.alpha',pssh:{__text:pssh}};}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=undefined,adaptations=undefined,contentProtection=undefined,KID=undefined,timestampOffset=undefined,startTime=undefined,segments=undefined,timescale=undefined,i=undefined,j=undefined; // 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;manifest.minBufferTime = mediaPlayerModel.getStableBufferTime();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)
} // In case of live streams, set availabilityStartTime property according to DVRWindowLength
if(manifest.type === 'dynamic' && manifest.timeShiftBufferDepth < Infinity){manifest.availabilityStartTime = new Date(manifestLoadedTime.getTime() - manifest.timeShiftBufferDepth * 1000);manifest.refreshManifestOnSwitchTrack = true;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(protectionHeader,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(manifest.type === 'dynamic'){ // Set availabilityStartTime for infinite DVR Window from segment timeline duration
if(manifest.timeShiftBufferDepth === Infinity){manifest.availabilityStartTime = new Date(manifestLoadedTime.getTime() - adaptations[i].SegmentTemplate.SegmentTimeline.duration * 1000);} // Match timeShiftBufferDepth to video segment timeline duration
if(manifest.timeShiftBufferDepth > 0 && manifest.timeShiftBufferDepth !== Infinity && adaptations[i].contentType === 'video' && 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=null; //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 the 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 */module.exports = exports['default'];
//# sourceMappingURL=MssParser.js.map