Skip to content

Commit

Permalink
fix: Sync each segment against EXT-X-PROGRAM-DATE-TIME (shaka-project…
Browse files Browse the repository at this point in the history
  • Loading branch information
joeyparrish committed Jan 12, 2023
1 parent 52f4b63 commit 50c9df4
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 20 deletions.
43 changes: 32 additions & 11 deletions lib/hls/hls_parser.js
Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -531,22 +535,29 @@ 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.offsetStreamInfo_(streamInfo, streamOffset);

this.offsetStream_(streamInfo, streamOffset);
// This is computed across all segments separately to manage
// accumulated drift in durations.
for (const segment of segmentIndex) {
segment.syncAgainst(lowestSyncTime);
}
}
}
}
Expand All @@ -557,13 +568,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) {
Expand Down Expand Up @@ -2765,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;
}

Expand Down
10 changes: 1 addition & 9 deletions lib/media/segment_index.js
Expand Up @@ -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);
}
}
}
Expand Down
39 changes: 39 additions & 0 deletions lib/media/segment_reference.js
Expand Up @@ -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');


Expand Down Expand Up @@ -383,6 +384,44 @@ 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;
}
}

/**
* 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;
if (Math.abs(offset) >= 0.001) {
this.offset(offset);
}
}
};


Expand Down

0 comments on commit 50c9df4

Please sign in to comment.