streaming_utils_BoxParser.js
/**
* 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.
*/
import Debug from '../../core/Debug';
import IsoFile from './IsoFile';
import FactoryMaker from '../../core/FactoryMaker';
import ISOBoxer from 'codem-isoboxer';
import IsoBoxSearchInfo from '../vo/IsoBoxSearchInfo';
function BoxParser(/*config*/) {
let logger,
instance;
let context = this.context;
function setup() {
logger = Debug(context).getInstance().getLogger(instance);
}
/**
* @param {ArrayBuffer} data
* @returns {IsoFile|null}
* @memberof BoxParser#
*/
function parse(data) {
if (!data) return null;
if (data.fileStart === undefined) {
data.fileStart = 0;
}
let parsedFile = ISOBoxer.parseBuffer(data);
let dashIsoFile = IsoFile(context).create();
dashIsoFile.setData(parsedFile);
return dashIsoFile;
}
/**
* From the list of type boxes to look for, returns the latest one that is fully completed (header + payload). This
* method only looks into the list of top boxes and doesn't analyze nested boxes.
* @param {string[]} types
* @param {ArrayBuffer|uint8Array} buffer
* @param {number} offset
* @returns {IsoBoxSearchInfo}
* @memberof BoxParser#
*/
function findLastTopIsoBoxCompleted(types, buffer, offset) {
if (offset === undefined) {
offset = 0;
}
// 8 = size (uint32) + type (4 characters)
if (!buffer || offset + 8 >= buffer.byteLength) {
return new IsoBoxSearchInfo(0, false);
}
const data = (buffer instanceof ArrayBuffer) ? new Uint8Array(buffer) : buffer;
let boxInfo;
let lastCompletedOffset = 0;
while (offset < data.byteLength) {
const boxSize = parseUint32(data, offset);
const boxType = parseIsoBoxType(data, offset + 4);
if (boxSize === 0) {
break;
}
if (offset + boxSize <= data.byteLength) {
if (types.indexOf(boxType) >= 0) {
boxInfo = new IsoBoxSearchInfo(offset, true, boxSize);
} else {
lastCompletedOffset = offset + boxSize;
}
}
offset += boxSize;
}
if (!boxInfo) {
return new IsoBoxSearchInfo(lastCompletedOffset, false);
}
return boxInfo;
}
function getSamplesInfo(ab) {
if (!ab || ab.byteLength === 0) {
return {sampleList: [], lastSequenceNumber: NaN, totalDuration: NaN, numSequences: NaN};
}
let isoFile = parse(ab);
// zero or more moofs
let moofBoxes = isoFile.getBoxes('moof');
// exactly one mfhd per moof
let mfhdBoxes = isoFile.getBoxes('mfhd');
let sampleDuration,
sampleCompositionTimeOffset,
sampleCount,
sampleSize,
sampleDts,
sampleList,
sample,
i, j, k, l, m, n,
dataOffset,
lastSequenceNumber,
numSequences,
totalDuration;
numSequences = isoFile.getBoxes('moof').length;
lastSequenceNumber = mfhdBoxes[mfhdBoxes.length - 1].sequence_number;
sampleCount = 0;
sampleList = [];
let subsIndex = -1;
let nextSubsSample = -1;
for (l = 0; l < moofBoxes.length; l++) {
let moofBox = moofBoxes[l];
// zero or more trafs per moof
let trafBoxes = moofBox.getChildBoxes('traf');
for (j = 0; j < trafBoxes.length; j++) {
let trafBox = trafBoxes[j];
// exactly one tfhd per traf
let tfhdBox = trafBox.getChildBox('tfhd');
// zero or one tfdt per traf
let tfdtBox = trafBox.getChildBox('tfdt');
sampleDts = tfdtBox.baseMediaDecodeTime;
// zero or more truns per traf
let trunBoxes = trafBox.getChildBoxes('trun');
// zero or more subs per traf
let subsBoxes = trafBox.getChildBoxes('subs');
for (k = 0; k < trunBoxes.length; k++) {
let trunBox = trunBoxes[k];
sampleCount = trunBox.sample_count;
dataOffset = (tfhdBox.base_data_offset || 0) + (trunBox.data_offset || 0);
for (i = 0; i < sampleCount; i++) {
sample = trunBox.samples[i];
sampleDuration = (sample.sample_duration !== undefined) ? sample.sample_duration : tfhdBox.default_sample_duration;
sampleSize = (sample.sample_size !== undefined) ? sample.sample_size : tfhdBox.default_sample_size;
sampleCompositionTimeOffset = (sample.sample_composition_time_offset !== undefined) ? sample.sample_composition_time_offset : 0;
let sampleData = {
'dts': sampleDts,
'cts': (sampleDts + sampleCompositionTimeOffset),
'duration': sampleDuration,
'offset': moofBox.offset + dataOffset,
'size': sampleSize,
'subSizes': [sampleSize]
};
if (subsBoxes) {
for (m = 0; m < subsBoxes.length; m++) {
let subsBox = subsBoxes[m];
if (subsIndex < (subsBox.entry_count - 1) && i > nextSubsSample) {
subsIndex++;
nextSubsSample += subsBox.entries[subsIndex].sample_delta;
}
if (i == nextSubsSample) {
sampleData.subSizes = [];
let entry = subsBox.entries[subsIndex];
for (n = 0; n < entry.subsample_count; n++) {
sampleData.subSizes.push(entry.subsamples[n].subsample_size);
}
}
}
}
sampleList.push(sampleData);
dataOffset += sampleSize;
sampleDts += sampleDuration;
}
}
totalDuration = sampleDts - tfdtBox.baseMediaDecodeTime;
}
}
return {sampleList: sampleList, lastSequenceNumber: lastSequenceNumber, totalDuration: totalDuration, numSequences: numSequences};
}
function getMediaTimescaleFromMoov(ab) {
let isoFile = parse(ab);
let mdhdBox = isoFile ? isoFile.getBox('mdhd') : undefined;
return mdhdBox ? mdhdBox.timescale : NaN;
}
function parseUint32(data, offset) {
return data[offset + 3] >>> 0 |
(data[offset + 2] << 8) >>> 0 |
(data[offset + 1] << 16) >>> 0 |
(data[offset] << 24) >>> 0;
}
function parseIsoBoxType(data, offset) {
return String.fromCharCode(data[offset++]) +
String.fromCharCode(data[offset++]) +
String.fromCharCode(data[offset++]) +
String.fromCharCode(data[offset]);
}
function findInitRange(data) {
let initRange = null;
let start,
end;
const isoFile = parse(data);
if (!isoFile) {
return initRange;
}
const ftyp = isoFile.getBox('ftyp');
const moov = isoFile.getBox('moov');
logger.debug('Searching for initialization.');
if (moov && moov.isComplete) {
start = ftyp ? ftyp.offset : moov.offset;
end = moov.offset + moov.size - 1;
initRange = start + '-' + end;
logger.debug('Found the initialization. Range: ' + initRange);
}
return initRange;
}
/**
* Real-time parsing (whenever data is loaded in the buffer payload) of the payload to capture the moof of a chunk
* @param {array} types
* @param {ArrayBuffer} buffer
* @param {number} offset
* @return {IsoBoxSearchInfo}
*/
function parsePayload(types, buffer, offset) {
if (offset === undefined) {
offset = 0;
}
if (!buffer || offset + 8 >= buffer.byteLength) {
return new IsoBoxSearchInfo(0, false);
}
const data = (buffer instanceof ArrayBuffer) ? new Uint8Array(buffer) : buffer;
let boxInfo;
let lastCompletedOffset = 0;
while (offset < data.byteLength) {
const boxSize = parseUint32(data, offset);
const boxType = parseIsoBoxType(data, offset + 4);
if (boxSize === 0) {
break;
}
if (offset + boxSize <= data.byteLength) {
if (types.indexOf(boxType) >= 0) {
boxInfo = new IsoBoxSearchInfo(offset, true, boxSize, boxType);
} else {
lastCompletedOffset = offset + boxSize;
}
}
offset += boxSize;
}
if (!boxInfo) {
return new IsoBoxSearchInfo(lastCompletedOffset, false);
}
return boxInfo;
}
instance = {
parse,
findLastTopIsoBoxCompleted,
getMediaTimescaleFromMoov,
getSamplesInfo,
findInitRange,
parsePayload
};
setup();
return instance;
}
BoxParser.__dashjs_factory_name = 'BoxParser';
export default FactoryMaker.getSingletonFactory(BoxParser);