mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
fix(DASH): Handle external SegmentBase BaseURL and SIDX offset fallback (#9477)
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>
This commit is contained in:
@@ -26,13 +26,15 @@ shaka.dash.Mp4SegmentIndexParser = class {
|
||||
* @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) {
|
||||
appendWindowStart, appendWindowEnd, indexIsExternal) {
|
||||
const Mp4SegmentIndexParser = shaka.dash.Mp4SegmentIndexParser;
|
||||
|
||||
/** @type {!Array<!shaka.media.SegmentReference>} */
|
||||
let references;
|
||||
|
||||
const parser = new shaka.util.Mp4Parser()
|
||||
@@ -44,7 +46,8 @@ shaka.dash.Mp4SegmentIndexParser = class {
|
||||
appendWindowStart,
|
||||
appendWindowEnd,
|
||||
uris,
|
||||
box);
|
||||
box,
|
||||
indexIsExternal);
|
||||
});
|
||||
|
||||
if (sidxData) {
|
||||
@@ -74,12 +77,13 @@ shaka.dash.Mp4SegmentIndexParser = class {
|
||||
* @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) {
|
||||
appendWindowEnd, uris, box, indexIsExternal = false) {
|
||||
goog.asserts.assert(
|
||||
box.version != null,
|
||||
'SIDX is a full box and should have a valid version.');
|
||||
@@ -119,7 +123,11 @@ shaka.dash.Mp4SegmentIndexParser = class {
|
||||
|
||||
// Subtract the presentation time offset
|
||||
let unscaledStartTime = earliestPresentationTime;
|
||||
let startByte = sidxOffset + box.size + firstOffset;
|
||||
// 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|.
|
||||
|
||||
@@ -189,14 +189,20 @@ shaka.dash.SegmentBase = class {
|
||||
const appendWindowEnd = periodDuration ?
|
||||
periodStart + periodDuration : Infinity;
|
||||
|
||||
// Segment references should resolve against the Representation's base URIs
|
||||
const segUris = context.representation.getBaseUris();
|
||||
// Check if the index URIs are different from the base URIs (i.e. external).
|
||||
const indexIsExternal = segUris.length != uris.length ||
|
||||
segUris.some((uri, i) => uri != uris[i]);
|
||||
|
||||
if (containerType == 'mp4') {
|
||||
references = shaka.dash.Mp4SegmentIndexParser.parse(
|
||||
indexData, startByte, uris, initSegmentReference, timestampOffset,
|
||||
appendWindowStart, appendWindowEnd);
|
||||
indexData, startByte, segUris, initSegmentReference, timestampOffset,
|
||||
appendWindowStart, appendWindowEnd, indexIsExternal);
|
||||
} else {
|
||||
goog.asserts.assert(initData, 'WebM requires init data');
|
||||
references = shaka.dash.WebmSegmentIndexParser.parse(
|
||||
indexData, initData, uris, initSegmentReference, timestampOffset,
|
||||
indexData, initData, segUris, initSegmentReference, timestampOffset,
|
||||
appendWindowStart, appendWindowEnd);
|
||||
}
|
||||
for (const ref of references) {
|
||||
|
||||
@@ -316,6 +316,72 @@ describe('DashParser SegmentBase', () => {
|
||||
expect(reference.endTime).toBe(10); // would be 12 without PTO
|
||||
});
|
||||
|
||||
it('fetch RepresentationIndex from init, media from BaseURL', async () => {
|
||||
// Matches the pattern seen in the wild: Representation points to media,
|
||||
// but RepresentationIndex/Initialization live in a shared init file.
|
||||
const source = [
|
||||
'<MPD mediaPresentationDuration="PT10S">',
|
||||
' <Period>',
|
||||
' <AdaptationSet mimeType="video/mp4">',
|
||||
' <Representation bandwidth="1">',
|
||||
' <BaseURL>https://media.example.com/video_1.mp4</BaseURL>',
|
||||
' <SegmentBase timescale="24000">',
|
||||
' <Initialization range="80-751" sourceURL="video_init.mp4"/>',
|
||||
' <RepresentationIndex range="0-79"' +
|
||||
' sourceURL="video_init.mp4"/>',
|
||||
' </SegmentBase>',
|
||||
' </Representation>',
|
||||
' </AdaptationSet>',
|
||||
' </Period>',
|
||||
'</MPD>',
|
||||
].join('\n');
|
||||
|
||||
// Minimal SIDX with one ref starting at byte 0, size 1000.
|
||||
const makeSidx = () => {
|
||||
const size = 44;
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const dv = new DataView(buffer);
|
||||
dv.setUint32(0, size);
|
||||
dv.setUint8(4, 's'.charCodeAt(0));
|
||||
dv.setUint8(5, 'i'.charCodeAt(0));
|
||||
dv.setUint8(6, 'd'.charCodeAt(0));
|
||||
dv.setUint8(7, 'x'.charCodeAt(0));
|
||||
dv.setUint32(8, 0); // version/flags
|
||||
dv.setUint32(12, 0); // reference_ID
|
||||
dv.setUint32(16, 24000);// timescale
|
||||
dv.setUint32(20, 0); // earliestPresentationTime
|
||||
dv.setUint32(24, 0); // firstOffset
|
||||
dv.setUint16(28, 0); // reserved
|
||||
dv.setUint16(30, 1); // referenceCount
|
||||
dv.setUint32(32, 1000); // referenceSize
|
||||
dv.setUint32(36, 24000);// subsegmentDuration (1s)
|
||||
dv.setUint32(40, 0); // SAP
|
||||
return buffer;
|
||||
};
|
||||
|
||||
fakeNetEngine
|
||||
.setResponseText('dummy://foo', source)
|
||||
.setResponseValue(
|
||||
'https://media.example.com/video_init.mp4', makeSidx());
|
||||
|
||||
/** @type {shaka.extern.Manifest} */
|
||||
const manifest = await parser.start('dummy://foo', playerInterface);
|
||||
const video = manifest.variants[0].video;
|
||||
await video.createSegmentIndex();
|
||||
goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!');
|
||||
|
||||
expect(fakeNetEngine.request).toHaveBeenCalledTimes(2);
|
||||
fakeNetEngine.expectRangeRequest(
|
||||
'https://media.example.com/video_init.mp4',
|
||||
0, 79, /* isInit= */ false);
|
||||
|
||||
const reference = Array.from(video.segmentIndex)[0];
|
||||
expect(reference.getUris()).toEqual(
|
||||
['https://media.example.com/video_1.mp4']);
|
||||
expect(reference.startByte).toBe(0);
|
||||
expect(reference.endByte).toBe(999);
|
||||
});
|
||||
|
||||
// https://github.com/shaka-project/shaka-player/issues/3230
|
||||
it('works with multi-Period with eviction', async () => {
|
||||
const source = [
|
||||
|
||||
Reference in New Issue
Block a user