diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index c6898e75cc..6e88ddca35 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -513,6 +513,11 @@ shaka.dash.DashParser = class { } presentationTimeline.setClockOffset(offset); } + + // This is the first point where we have a meaningful presentation start + // time, and we need to tell PresentationTimeline that so that it can + // maintain consistency from here on. + presentationTimeline.lockStartTime(); } else { // Just update the variants and text streams, which may change as periods // are added or removed. diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index bb501da250..787eaeb2f0 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -740,8 +740,14 @@ shaka.hls.HlsParser = class { for (const stream of streamsToNotify) { this.segmentsToNotifyByStream_.push(stream.segmentIndex.references); } + this.notifySegments_(); + // This is the first point where we have a meaningful presentation start + // time, and we need to tell PresentationTimeline that so that it can + // maintain consistency from here on. + this.presentationTimeline_.lockStartTime(); + // This asserts that the live edge is being calculated from segment times. // For VOD and event streams, this check should still pass. goog.asserts.assert( @@ -1859,8 +1865,6 @@ shaka.hls.HlsParser = class { /* presentationStartTime= */ null, /* delay= */ 0); this.presentationTimeline_.setStatic(true); } - - this.notifySegments_(); } /** diff --git a/lib/media/presentation_timeline.js b/lib/media/presentation_timeline.js index ba3ea3ec54..0a18b41405 100644 --- a/lib/media/presentation_timeline.js +++ b/lib/media/presentation_timeline.js @@ -93,6 +93,9 @@ shaka.media.PresentationTimeline = class { * @private {number} */ this.availabilityTimeOffset_ = 0; + + /** @private {boolean} */ + this.startTimeLocked_ = false; } @@ -229,7 +232,8 @@ shaka.media.PresentationTimeline = class { this.maxSegmentEndTime_ = Math.max(this.maxSegmentEndTime_, lastReferenceEndTime); - if (this.presentationStartTime_ != null && this.autoCorrectDrift_) { + if (this.presentationStartTime_ != null && this.autoCorrectDrift_ && + !this.startTimeLocked_) { // Since we have explicit segment end times, calculate a presentation // start based on them. This start time accounts for drift. // Date.now() is in milliseconds, from which we compute "now" in seconds. @@ -243,6 +247,25 @@ shaka.media.PresentationTimeline = class { } + /** + * Lock the presentation timeline's start time. After this is called, no + * further adjustments to presentationStartTime_ will be permitted. + * + * This should be called after all Periods have been parsed, and all calls to + * notifySegments() from the initial manifest parse have been made. + * + * Without this, we can get assertion failures in SegmentIndex for certain + * DAI content. If DAI adds ad segments to the manifest faster than + * real-time, adjustments to presentationStartTime_ can cause availability + * windows to jump around on updates. + * + * @export + */ + lockStartTime() { + this.startTimeLocked_ = true; + } + + /** * Gives PresentationTimeline a Stream's minimum segment start time. *