mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
42df30a84e
This pull request improves support for external SIDX (Segment Index) files in DASH manifests, particularly when the `RepresentationIndex` uses a different `BaseURL` or `sourceURL` than the media itself. It also enhances base64 decoding robustness and adds a new unit test to verify correct behavior. **DASH SIDX and Segment Reference Handling:** * Enhanced `Mp4SegmentIndexParser.parse` to accept an `indexIsExternal` parameter, enabling correct parsing of SIDX files that are external to the media and may have different base URIs. The parser now adjusts the offset logic for external indices. [[1]](diffhunk://#diff-6435d27cfd56024b0920175aa9a6992242d18900d27f7edfaa77d89673a8dd0aR29-R37) [[2]](diffhunk://#diff-6435d27cfd56024b0920175aa9a6992242d18900d27f7edfaa77d89673a8dd0aR54-L63) * Addresses #6091: Updated `SegmentBase.generateSegmentIndexFromUris` to detect when the index URI is external by comparing the base URIs, and to pass this information to the parser. This ensures that segment references are resolved against the correct URIs. **Robustness Improvements:** * Improved base64 decoding in `Uint8ArrayUtils.fromBase64` by normalizing padding, handling cases where the input string omits trailing `=` characters. **Testing Enhancements:** * Added a unit test to verify that `RepresentationIndex` with a different `BaseURL` or `sourceURL` is correctly honored, ensuring that segment index requests use the proper URI and range. --------- Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
179 lines
5.8 KiB
JavaScript
179 lines
5.8 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.dash.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.Mp4Parser');
|
|
|
|
|
|
shaka.dash.Mp4SegmentIndexParser = class {
|
|
/**
|
|
* Parses SegmentReferences from an ISO BMFF SIDX structure.
|
|
* @param {BufferSource} 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} timestampOffset
|
|
* @param {number} appendWindowStart
|
|
* @param {number} appendWindowEnd
|
|
* @param {boolean=} indexIsExternal sidx files are external to the media
|
|
* @return {!Array<!shaka.media.SegmentReference>}
|
|
*/
|
|
static parse(
|
|
sidxData, sidxOffset, uris, initSegmentReference, timestampOffset,
|
|
appendWindowStart, appendWindowEnd, indexIsExternal) {
|
|
const Mp4SegmentIndexParser = shaka.dash.Mp4SegmentIndexParser;
|
|
|
|
/** @type {!Array<!shaka.media.SegmentReference>} */
|
|
let references;
|
|
|
|
const parser = new shaka.util.Mp4Parser()
|
|
.fullBox('sidx', (box) => {
|
|
references = Mp4SegmentIndexParser.parseSIDX_(
|
|
sidxOffset,
|
|
initSegmentReference,
|
|
timestampOffset,
|
|
appendWindowStart,
|
|
appendWindowEnd,
|
|
uris,
|
|
box,
|
|
indexIsExternal);
|
|
});
|
|
|
|
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} timestampOffset
|
|
* @param {number} appendWindowStart
|
|
* @param {number} appendWindowEnd
|
|
* @param {!Array<string>} uris The possible locations of the MP4 file that
|
|
* contains the segments.
|
|
* @param {!shaka.extern.ParsedBox} box
|
|
* @param {boolean=} indexIsExternal sidx file is external to the media
|
|
* @return {!Array<!shaka.media.SegmentReference>}
|
|
* @private
|
|
*/
|
|
static parseSIDX_(
|
|
sidxOffset, initSegmentReference, timestampOffset, appendWindowStart,
|
|
appendWindowEnd, uris, box, indexIsExternal = false) {
|
|
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;
|
|
// For external SIDX (index fetched from a different file), the spec
|
|
// anchors first_offset at the start of the media segment. Re-anchor by
|
|
// subtracting the box size so the initial start byte is |firstOffset|.
|
|
const anchorOffset = indexIsExternal ? -box.size : sidxOffset;
|
|
let startByte = anchorOffset + box.size + firstOffset;
|
|
|
|
for (let i = 0; i < referenceCount; i++) {
|
|
// |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('Hierarchical 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(
|
|
nativeStartTime + timestampOffset,
|
|
nativeEndTime + timestampOffset,
|
|
(() => { return uris; }),
|
|
startByte,
|
|
startByte + referenceSize - 1,
|
|
initSegmentReference,
|
|
timestampOffset,
|
|
appendWindowStart,
|
|
appendWindowEnd));
|
|
|
|
unscaledStartTime += subsegmentDuration;
|
|
startByte += referenceSize;
|
|
}
|
|
|
|
box.parser.stop();
|
|
return references;
|
|
}
|
|
};
|