diff --git a/src/parsers/manifest/dash/common/__tests__/manifest_bounds_calculator.test.ts b/src/parsers/manifest/dash/common/__tests__/manifest_bounds_calculator.test.ts index 0f10c12c4d..51ea24cd19 100644 --- a/src/parsers/manifest/dash/common/__tests__/manifest_bounds_calculator.test.ts +++ b/src/parsers/manifest/dash/common/__tests__/manifest_bounds_calculator.test.ts @@ -9,9 +9,9 @@ describe("DASH parsers - ManifestBoundsCalculator", () => { availabilityStartTime: 0, serverTimestampOffset: undefined, }); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(undefined); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(undefined); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(undefined); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual(undefined); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(3)).toEqual(undefined); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(5)).toEqual(undefined); }); it("should return 0 through `getEstimatedMinimumSegmentTime` for a static content", () => { @@ -21,10 +21,10 @@ describe("DASH parsers - ManifestBoundsCalculator", () => { availabilityStartTime: 0, serverTimestampOffset: 555555, }); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(0); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual(0); manifestBoundsCalculator.setLastPosition(5555, 2135); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(0); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(0); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(4)).toEqual(0); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(5)).toEqual(0); }); it("should return 0 through `getEstimatedMinimumSegmentTime` if the `serverTimestampOffset` was never set nor the last position for a dynamic content with no timeShiftBufferDepth", () => { @@ -34,9 +34,9 @@ describe("DASH parsers - ManifestBoundsCalculator", () => { availabilityStartTime: 0, serverTimestampOffset: undefined, }); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(0); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(0); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(0); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual(0); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(5)).toEqual(0); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(28)).toEqual(0); }); it("should return `false` through `lastPositionIsKnown` if `setLastPositionOffset` was never called", () => { @@ -47,7 +47,7 @@ describe("DASH parsers - ManifestBoundsCalculator", () => { serverTimestampOffset: undefined, }); expect(manifestBoundsCalculator.lastPositionIsKnown()).toEqual(false); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(undefined); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual(undefined); expect(manifestBoundsCalculator.lastPositionIsKnown()).toEqual(false); }); @@ -86,9 +86,10 @@ describe("DASH parsers - ManifestBoundsCalculator", () => { }); manifestBoundsCalculator.setLastPosition(1000, 10); performanceNow = 25000; - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(1010); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual(1010); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(50)).toEqual(960); performanceNow = 35000; - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(1020); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual(1020); mockPerformanceNow.mockRestore(); }); @@ -104,18 +105,27 @@ describe("DASH parsers - ManifestBoundsCalculator", () => { serverTimestampOffset: 7000, }); manifestBoundsCalculator.setLastPosition(3000, 10); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual( + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual( 7 + 5 - 4 - 3, ); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(4)).toEqual( + 7 + 5 - 4 - 3 - 4, + ); performanceNow = 25000; - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual( + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual( 7 + 25 - 4 - 3, ); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(10)).toEqual( + 7 + 25 - 4 - 3 - 10, + ); performanceNow = 35000; manifestBoundsCalculator.setLastPosition(84546464, 5642); - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual( + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual( 7 + 35 - 4 - 3, ); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(5)).toEqual( + 7 + 35 - 4 - 3 - 5, + ); mockPerformanceNow.mockRestore(); }); @@ -132,10 +142,12 @@ describe("DASH parsers - ManifestBoundsCalculator", () => { }); manifestBoundsCalculator.setLastPosition(1000, 0); performanceNow = 50000; - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(1045); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual(1045); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(40)).toEqual(1005); manifestBoundsCalculator.setLastPosition(0, 0); performanceNow = 55000; - expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime()).toEqual(50); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(0)).toEqual(50); + expect(manifestBoundsCalculator.getEstimatedMinimumSegmentTime(40)).toEqual(10); mockPerformanceNow.mockRestore(); }); diff --git a/src/parsers/manifest/dash/common/indexes/template.ts b/src/parsers/manifest/dash/common/indexes/template.ts index b2c513abd4..df3dca4586 100644 --- a/src/parsers/manifest/dash/common/indexes/template.ts +++ b/src/parsers/manifest/dash/common/indexes/template.ts @@ -557,7 +557,9 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex } const { duration, timescale } = this._index; - const firstPosition = this._manifestBoundsCalculator.getEstimatedMinimumSegmentTime(); + const firstPosition = this._manifestBoundsCalculator.getEstimatedMinimumSegmentTime( + duration / timescale, + ); if (firstPosition === undefined) { return undefined; } diff --git a/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts b/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts index 0c4bfb1652..284ebaaefc 100644 --- a/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +++ b/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts @@ -796,7 +796,9 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex if (!this._isDynamic) { return; } - const firstPosition = this._manifestBoundsCalculator.getEstimatedMinimumSegmentTime(); + const firstPosition = this._manifestBoundsCalculator.getEstimatedMinimumSegmentTime( + (this._index.timeline[0]?.duration ?? 0) / this._index.timescale, + ); if (isNullOrUndefined(firstPosition)) { return; // we don't know yet } diff --git a/src/parsers/manifest/dash/common/manifest_bounds_calculator.ts b/src/parsers/manifest/dash/common/manifest_bounds_calculator.ts index 9b16dc95f6..12994f3b71 100644 --- a/src/parsers/manifest/dash/common/manifest_bounds_calculator.ts +++ b/src/parsers/manifest/dash/common/manifest_bounds_calculator.ts @@ -109,10 +109,14 @@ export default class ManifestBoundsCalculator { /** * Estimate a minimum bound for the content from the last set segment time * and buffer depth. - * Consider that it is only an estimation, not the real value. + * Consider that it is only an estimate, not the real value. + * @param {number} segmentDuration - In DASH, the buffer depth actually also + * depend on a corresponding's segment duration (e.g. a segment become + * unavailable once the `timeShiftBufferDepth` + its duration has elapsed). + * This argument can thus be set the approximate duration of a segment. * @return {number|undefined} */ - getEstimatedMinimumSegmentTime(): number | undefined { + getEstimatedMinimumSegmentTime(segmentDuration: number): number | undefined { if (!this._isDynamic || this._timeShiftBufferDepth === null) { return 0; } @@ -121,7 +125,7 @@ export default class ManifestBoundsCalculator { if (maximumBound === undefined) { return undefined; } - const minimumBound = maximumBound - this._timeShiftBufferDepth; + const minimumBound = maximumBound - (this._timeShiftBufferDepth + segmentDuration); return minimumBound; } diff --git a/src/parsers/manifest/dash/common/parse_mpd.ts b/src/parsers/manifest/dash/common/parse_mpd.ts index 5664b5e88f..ef32c1dfdb 100644 --- a/src/parsers/manifest/dash/common/parse_mpd.ts +++ b/src/parsers/manifest/dash/common/parse_mpd.ts @@ -258,6 +258,7 @@ function parseCompleteIntermediateRepresentation( args.referenceDateTime, ); const timeShiftBufferDepth = rootAttributes.timeShiftBufferDepth; + const maxSegmentDuration = rootAttributes.maxSegmentDuration; const { externalClockOffset: clockOffset, unsafelyBaseOnPreviousManifest } = args; const { externalClockOffset } = args; @@ -279,7 +280,6 @@ function parseCompleteIntermediateRepresentation( manifestBoundsCalculator, manifestProfiles: mpdIR.attributes.profiles, receivedTime: args.manifestReceivedTime, - timeShiftBufferDepth, unsafelyBaseOnPreviousManifest, xlinkInfos, xmlNamespaces: mpdIR.attributes.namespaces, @@ -381,6 +381,20 @@ function parseCompleteIntermediateRepresentation( // can go even lower in terms of depth minimumTime = minimumSafePosition; timeshiftDepth = timeShiftBufferDepth ?? null; + if (timeshiftDepth !== null) { + // The DASH spec implies that a segment is still available after a given + // `timeShiftBufferDepth` for a time equal to its duration + // (What I interpret from "ISO/IEC 23009-1 fifth edition 2022-08 + // A.3.4 Media Segment list restrictions). + // + // This `timeshiftDepth` property is global for the whole Manifest (and + // not per segment), thus we cannot do exactly that, but we can take the + // anounced `maxSegmentDuration` by default instead. This may be a little + // too optimistic, but would in reality not lead to a lot of issues as + // this `timeshiftDepth` property is not the one that should be relied on + // to know which segment can or cannot be requested anymore. + timeshiftDepth += maxSegmentDuration ?? 0; + } if ( timeshiftDepth !== null && minimumTime !== undefined && diff --git a/tests/integration/scenarios/dash_live.test.js b/tests/integration/scenarios/dash_live.test.js index 60ef2ff187..01ba31aa64 100644 --- a/tests/integration/scenarios/dash_live.test.js +++ b/tests/integration/scenarios/dash_live.test.js @@ -250,8 +250,8 @@ describe("DASH live content (SegmentTimeline)", function () { }); expect(manifestLoaderCalledTimes).to.equal(1); - await checkAfterSleepWithBackoff(null, () => { - expect(player.getMinimumPosition()).to.be.closeTo(1527507768, 1); + await checkAfterSleepWithBackoff({ maxTimeMs: 2000 }, () => { + expect(player.getMinimumPosition()).to.be.closeTo(1527507763, 1); }); }); }); diff --git a/tests/integration/scenarios/dash_live_multi_periods.test.js b/tests/integration/scenarios/dash_live_multi_periods.test.js index 54232e6cf0..247c5f7fbe 100644 --- a/tests/integration/scenarios/dash_live_multi_periods.test.js +++ b/tests/integration/scenarios/dash_live_multi_periods.test.js @@ -57,7 +57,7 @@ describe("DASH live content multi-periods (SegmentTemplate)", function () { const maxPos = player.getMaximumPosition(); expect(maxPos).to.be.closeTo(now, 2); const minPos = player.getMinimumPosition(); - expect(minPos).to.be.closeTo(now - manifestInfos.tsbd, 2); + expect(minPos).to.be.closeTo(now - manifestInfos.tsbd - 2, 2); expect(manifestLoaderCalledTimes).to.equal(1); }); }); diff --git a/tests/integration/scenarios/dash_live_utc_timings.test.js b/tests/integration/scenarios/dash_live_utc_timings.test.js index ed4c069c92..854c858629 100644 --- a/tests/integration/scenarios/dash_live_utc_timings.test.js +++ b/tests/integration/scenarios/dash_live_utc_timings.test.js @@ -31,7 +31,7 @@ describe("DASH live - UTCTimings", () => { { maxTimeMs: 1000 }, { resolveWhen() { - expect(player.getMinimumPosition()).to.be.closeTo(1553521448, 3); + expect(player.getMinimumPosition()).to.be.closeTo(1553521446, 3); expect(player.getMaximumPosition()).to.be.closeTo(1553521748, 3); }, untilSuccess() { @@ -56,7 +56,7 @@ describe("DASH live - UTCTimings", () => { { maxTimeMs: 1000 }, { resolveWhen() { - expect(player.getMinimumPosition()).to.be.closeTo(1553521748, 1); + expect(player.getMinimumPosition()).to.be.closeTo(1553521746, 1); expect(player.getMaximumPosition()).to.be.closeTo(1553522048, 1); }, untilSuccess() { @@ -95,7 +95,7 @@ describe("DASH live - UTCTimings", () => { { maxTimeMs: 1000 }, { resolveWhen() { - expect(player.getMinimumPosition()).to.be.closeTo(1558791848, 3); + expect(player.getMinimumPosition()).to.be.closeTo(1558791846, 3); }, untilSuccess() { expect(player.getMinimumPosition()).to.equal(null); @@ -119,7 +119,7 @@ describe("DASH live - UTCTimings", () => { { maxTimeMs: 1000 }, { resolveWhen() { - expect(player.getMinimumPosition()).to.be.closeTo(1553521748, 1); + expect(player.getMinimumPosition()).to.be.closeTo(1553521746, 1); expect(player.getMaximumPosition()).to.be.closeTo(1553522048, 1); }, untilSuccess() { @@ -158,7 +158,7 @@ describe("DASH live - UTCTimings", () => { Date.now() / 1000 - manifestInfos.availabilityStartTime; const minimumPosition = maximumPosition - timeShiftBufferDepth; - expect(player.getMinimumPosition()).to.be.closeTo(minimumPosition, 3); + expect(player.getMinimumPosition()).to.be.closeTo(minimumPosition - 2, 3); expect(player.getMaximumPosition()).to.be.closeTo(maximumPosition, 3); }, untilSuccess() { @@ -184,7 +184,7 @@ describe("DASH live - UTCTimings", () => { { maxTimeMs: 1000 }, { resolveWhen() { - expect(player.getMinimumPosition()).to.be.closeTo(1553521748, 1); + expect(player.getMinimumPosition()).to.be.closeTo(1553521746, 1); expect(player.getMaximumPosition()).to.be.closeTo(1553522048, 1); }, untilSuccess() { @@ -218,7 +218,7 @@ describe("DASH live - UTCTimings", () => { { maxTimeMs: 1000 }, { resolveWhen() { - expect(player.getMinimumPosition()).to.be.closeTo(1553521448, 3); + expect(player.getMinimumPosition()).to.be.closeTo(1553521446, 3); }, untilSuccess() { expect(player.getMinimumPosition()).to.equal(null); @@ -242,7 +242,7 @@ describe("DASH live - UTCTimings", () => { { maxTimeMs: 1000 }, { resolveWhen() { - expect(player.getMinimumPosition()).to.be.closeTo(1553521748, 1); + expect(player.getMinimumPosition()).to.be.closeTo(1553521746, 1); expect(player.getMaximumPosition()).to.be.closeTo(1553522048, 1); }, untilSuccess() {