From c7d34340c4e19be729388cb0863fc904f66012e7 Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Wed, 4 Jan 2023 13:11:25 -0800 Subject: [PATCH 1/4] Factor out offset method to ref --- lib/media/segment_index.js | 10 +--------- lib/media/segment_reference.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/media/segment_index.js b/lib/media/segment_index.js index 913ecdb55f..bad96a0343 100644 --- a/lib/media/segment_index.js +++ b/lib/media/segment_index.js @@ -183,15 +183,7 @@ shaka.media.SegmentIndex = class { offset(offset) { if (!this.immutable_) { for (const ref of this.references) { - ref.startTime += offset; - ref.endTime += offset; - ref.trueEndTime += offset; - - for (const partial of ref.partialReferences) { - partial.startTime += offset; - partial.endTime += offset; - partial.trueEndTime += offset; - } + ref.offset(offset); } } } diff --git a/lib/media/segment_reference.js b/lib/media/segment_reference.js index 38d1851504..66f5b3d7f5 100644 --- a/lib/media/segment_reference.js +++ b/lib/media/segment_reference.js @@ -383,6 +383,25 @@ shaka.media.SegmentReference = class { getThumbnailSprite() { return this.thumbnailSprite; } + + /** + * Offset the segment reference by a fixed amount. + * + * @param {number} offset The amount to add to the segment's start and end + * times. + * @export + */ + offset(offset) { + this.startTime += offset; + this.endTime += offset; + this.trueEndTime += offset; + + for (const partial of this.partialReferences) { + partial.startTime += offset; + partial.endTime += offset; + partial.trueEndTime += offset; + } + } }; From 7d0a11b311d0e9719a85e127b14d558936f05bce Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Wed, 4 Jan 2023 13:15:18 -0800 Subject: [PATCH 2/4] Pull out segment index updates from offsetStream --- lib/hls/hls_parser.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index fc442ba594..f0a74c5742 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -489,7 +489,11 @@ shaka.hls.HlsParser = class { // Now adjust timestamps back to begin at 0. const segmentN = segmentIndex.earliestReference(); if (segmentN) { - this.offsetStream_(streamInfo, -segmentN.startTime); + const streamOffset = -segmentN.startTime; + // Modify all SegmentReferences equally. + streamInfo.stream.segmentIndex.offset(streamOffset); + // Update other parts of streamInfo the same way. + this.offsetStreamInfo_(streamInfo, streamOffset); } } } @@ -531,22 +535,26 @@ shaka.hls.HlsParser = class { for (const streamInfo of this.uriToStreamInfosMap_.values()) { const segmentIndex = streamInfo.stream.segmentIndex; if (segmentIndex != null) { + // A segment's startTime should be based on its syncTime vs the lowest + // syncTime across all streams. The earliest segment sync time from + // any stream will become presentation time 0. If two streams start + // e.g. 6 seconds apart in syncTime, then their first segments will + // also start 6 seconds apart in presentation time. + const segment0 = segmentIndex.earliestReference(); if (segment0.syncTime == null) { shaka.log.alwaysError('Missing EXT-X-PROGRAM-DATE-TIME for stream', streamInfo.verbatimMediaPlaylistUri, 'Expect AV sync issues!'); } else { - // The first segment's target startTime should be based entirely on - // its syncTime. The rest of the stream will be based on that - // starting point. The earliest segment sync time from any stream - // will become presentation time 0. If two streams start e.g. 6 - // seconds apart in syncTime, then their first segments will also - // start 6 seconds apart in presentation time. + // Stream metadata are offset by a fixed amount based on the + // first segment. const segment0TargetTime = segment0.syncTime - lowestSyncTime; const streamOffset = segment0TargetTime - segment0.startTime; - this.offsetStream_(streamInfo, streamOffset); + // Modify all SegmentReferences equally. + streamInfo.stream.segmentIndex.offset(streamOffset); + this.offsetStreamInfo_(streamInfo, streamOffset); } } } @@ -557,13 +565,13 @@ shaka.hls.HlsParser = class { * @param {number} offset * @private */ - offsetStream_(streamInfo, offset) { - streamInfo.stream.segmentIndex.offset(offset); - + offsetStreamInfo_(streamInfo, offset) { + // Adjust our accounting of the maximum timestamp. streamInfo.maxTimestamp += offset; goog.asserts.assert(streamInfo.maxTimestamp >= 0, 'Negative maxTimestamp after adjustment!'); + // Update our map from sequence number to start time. const mediaSequenceToStartTime = this.getMediaSequenceToStartTimeFor_(streamInfo); for (const [key, value] of mediaSequenceToStartTime) { From 96ea2e5d20d759151bd5343530774632a71f06ad Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Wed, 4 Jan 2023 13:15:38 -0800 Subject: [PATCH 3/4] Sync each segment against PDT --- lib/hls/hls_parser.js | 19 ++++++++++++++++--- lib/media/segment_reference.js | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index f0a74c5742..a27c33580f 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -551,10 +551,13 @@ shaka.hls.HlsParser = class { // first segment. const segment0TargetTime = segment0.syncTime - lowestSyncTime; const streamOffset = segment0TargetTime - segment0.startTime; - - // Modify all SegmentReferences equally. - streamInfo.stream.segmentIndex.offset(streamOffset); this.offsetStreamInfo_(streamInfo, streamOffset); + + // This is computed across all segments separately to manage + // accumulated drift in durations. + for (const segment of segmentIndex) { + segment.syncAgainst(lowestSyncTime); + } } } } @@ -2773,6 +2776,16 @@ shaka.hls.HlsParser = class { } } + // lowestSyncTime is a value from a previous playlist update. Use it to + // set reference start times. If this is the first playlist parse, we will + // skip this step, and wait until we have sync time across stream types. + const lowestSyncTime = this.lowestSyncTime_; + if (someSyncTime && lowestSyncTime != Infinity) { + for (const reference of references) { + reference.syncAgainst(lowestSyncTime); + } + } + return references; } diff --git a/lib/media/segment_reference.js b/lib/media/segment_reference.js index 66f5b3d7f5..615200cdfe 100644 --- a/lib/media/segment_reference.js +++ b/lib/media/segment_reference.js @@ -8,6 +8,7 @@ goog.provide('shaka.media.InitSegmentReference'); goog.provide('shaka.media.SegmentReference'); goog.require('goog.asserts'); +goog.require('shaka.log'); goog.require('shaka.util.ArrayUtils'); @@ -402,6 +403,23 @@ shaka.media.SegmentReference = class { partial.trueEndTime += offset; } } + + /** + * Sync this segment against a particular sync time that will serve as "0" in + * the presentation timeline. + * + * @param {number} lowestSyncTime + * @export + */ + syncAgainst(lowestSyncTime) { + if (this.syncTime == null) { + shaka.log.alwaysError('Sync attempted without sync time!'); + return; + } + const desiredStart = this.syncTime - lowestSyncTime; + const offset = desiredStart - this.startTime; + this.offset(offset); + } }; From 29cdf35e9d3b3a84b77c5eb9ee0cf2939a0c0edb Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Wed, 11 Jan 2023 15:15:24 -0800 Subject: [PATCH 4/4] Only offset segments if the offset is greater than 1ms --- lib/media/segment_reference.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/media/segment_reference.js b/lib/media/segment_reference.js index 615200cdfe..cfde258c8f 100644 --- a/lib/media/segment_reference.js +++ b/lib/media/segment_reference.js @@ -418,7 +418,9 @@ shaka.media.SegmentReference = class { } const desiredStart = this.syncTime - lowestSyncTime; const offset = desiredStart - this.startTime; - this.offset(offset); + if (Math.abs(offset) >= 0.001) { + this.offset(offset); + } } };