Files
shaka-player/lib/media/mp4_segment_index_parser.js
T
Joey Parrish 7fe97b841b Move fields to SegmentReference
To prepare for flattening out the manifest structure to remove
periods, this change moves initSegmentReference and
presentationTimeOffset fields into the SegmentReference object.  This
way, the segments on either side of a period transition or HLS
discontinuity can have different offsets or init segments, eventually
allowing us to create a single array of SegmentReferences for
multi-period content.

Issue #1339

Change-Id: Ic7eff0483789644881247ecf8044c5fb6a48f0e6
2019-08-01 11:04:36 -07:00

184 lines
5.9 KiB
JavaScript

/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.media.Mp4SegmentIndexParser');
goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.media.InitSegmentReference');
goog.require('shaka.media.SegmentReference');
goog.require('shaka.util.Error');
goog.require('shaka.util.Iterables');
goog.require('shaka.util.Mp4Parser');
shaka.media.Mp4SegmentIndexParser = class {
/**
* Parses SegmentReferences from an ISO BMFF SIDX structure.
* @param {!ArrayBuffer} sidxData The MP4's container's SIDX.
* @param {number} sidxOffset The SIDX's offset, in bytes, from the start of
* the MP4 container.
* @param {!Array.<string>} uris The possible locations of the MP4 file that
* contains the segments.
* @param {shaka.media.InitSegmentReference} initSegmentReference
* @param {number} scaledPresentationTimeOffset
* @return {!Array.<!shaka.media.SegmentReference>}
* @throws {shaka.util.Error}
*/
static parse(
sidxData, sidxOffset, uris, initSegmentReference,
scaledPresentationTimeOffset) {
const Mp4SegmentIndexParser = shaka.media.Mp4SegmentIndexParser;
let references;
const parser = new shaka.util.Mp4Parser()
.fullBox('sidx', (box) => {
references = Mp4SegmentIndexParser.parseSIDX_(
sidxOffset,
initSegmentReference,
scaledPresentationTimeOffset,
uris,
box);
});
if (sidxData) {
parser.parse(sidxData);
}
if (references) {
return references;
} else {
shaka.log.error('Invalid box type, expected "sidx".');
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MEDIA,
shaka.util.Error.Code.MP4_SIDX_WRONG_BOX_TYPE);
}
}
/**
* Parse a SIDX box from the given reader.
*
* @param {number} sidxOffset
* @param {shaka.media.InitSegmentReference} initSegmentReference
* @param {number} scaledPresentationTimeOffset
* @param {!Array.<string>} uris The possible locations of the MP4 file that
* contains the segments.
* @param {!shaka.extern.ParsedBox} box
* @return {!Array.<!shaka.media.SegmentReference>}
* @private
*/
static parseSIDX_(
sidxOffset,
initSegmentReference,
scaledPresentationTimeOffset,
uris,
box) {
goog.asserts.assert(
box.version != null,
'SIDX is a full box and should have a valid version.');
const references = [];
// Parse the SIDX structure.
// Skip reference_ID (32 bits).
box.reader.skip(4);
const timescale = box.reader.readUint32();
if (timescale == 0) {
shaka.log.error('Invalid timescale.');
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MEDIA,
shaka.util.Error.Code.MP4_SIDX_INVALID_TIMESCALE);
}
let earliestPresentationTime;
let firstOffset;
if (box.version == 0) {
earliestPresentationTime = box.reader.readUint32();
firstOffset = box.reader.readUint32();
} else {
earliestPresentationTime = box.reader.readUint64();
firstOffset = box.reader.readUint64();
}
// Skip reserved (16 bits).
box.reader.skip(2);
// Add references.
const referenceCount = box.reader.readUint16();
// Subtract the presentation time offset
let unscaledStartTime = earliestPresentationTime;
let startByte = sidxOffset + box.size + firstOffset;
for (const _ of shaka.util.Iterables.range(referenceCount)) {
shaka.util.Functional.ignored(_);
// |chunk| is 1 bit for |referenceType|, and 31 bits for |referenceSize|.
const chunk = box.reader.readUint32();
const referenceType = (chunk & 0x80000000) >>> 31;
const referenceSize = chunk & 0x7FFFFFFF;
const subsegmentDuration = box.reader.readUint32();
// Skipping 1 bit for |startsWithSap|, 3 bits for |sapType|, and 28 bits
// for |sapDelta|.
box.reader.skip(4);
// If |referenceType| is 1 then the reference is to another SIDX.
// We do not support this.
if (referenceType == 1) {
shaka.log.error('Heirarchical SIDXs are not supported.');
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MEDIA,
shaka.util.Error.Code.MP4_SIDX_TYPE_NOT_SUPPORTED);
}
// The media timestamps inside the container.
const nativeStartTime = unscaledStartTime / timescale;
const nativeEndTime =
(unscaledStartTime + subsegmentDuration) / timescale;
references.push(
new shaka.media.SegmentReference(
references.length,
// Start and end times, relative to the period.
nativeStartTime - scaledPresentationTimeOffset,
nativeEndTime - scaledPresentationTimeOffset,
(() => { return uris; }),
startByte,
startByte + referenceSize - 1,
initSegmentReference,
// The offset that MSE will have to apply to get the period
// timestamps from the media timestamps.
scaledPresentationTimeOffset));
unscaledStartTime += subsegmentDuration;
startByte += referenceSize;
}
box.parser.stop();
return references;
}
};