Skip to content

Commit

Permalink
fix(HLS): Only offset segment ref times when needed w/ EXT-X-MEDIA-SE…
Browse files Browse the repository at this point in the history
…QUENCE (#6378)

Fixes #6377

When choosing to synchronize HLS streams using `EXT-X-MEDIA-SEQUENCE` instead of `EXT-X-PROGRAM-DATE-TIME` during LIVE playlist variant switches, Shaka unnecessarily drops 'old' segments and offsets the segment references of the new playlist so that the earliest reference represents media time `0`: https://github.com/shaka-project/shaka-player/blob/ea740ba2468f3b035d463ea9933aa7eeccf5c748/lib/hls/hls_parser.js#L610-L613

This is problematic, as the `StreamingEngine`'s media time used to download new segments is based off the latest segment references:
https://github.com/shaka-project/shaka-player/blob/ea740ba2468f3b035d463ea9933aa7eeccf5c748/lib/media/streaming_engine.js#L1248-L1250
https://github.com/shaka-project/shaka-player/blob/ea740ba2468f3b035d463ea9933aa7eeccf5c748/lib/media/streaming_engine.js#L1385

For example:

```
            Playlist download #1         
EXT-X-MEDIA-SEQUENCE       Media Time
           0                   0
           1                   6
           2                   12
           3                   18

           Playlist download #2 (what happens now)
EXT-X-MEDIA-SEQUENCE       Media Time
          6                   0
          7                   6
          8                   12
          9                   18

           Playlist download #2 (desired behavior)
EXT-X-MEDIA-SEQUENCE       Media Time
          6                   36
          7                   42
          8                   48
          9                   54

```

Without this fix, and given the above example, if Shaka tries to request the segment at `time=36`, it will fail because the media state only has segment references up to `time=18`. Until the manifests, 'catch up', the player freezes; this can be especially problematic when a large amount of time accumulates before a variant switch occurs.

This has been confirmed by Pluto TV to fix their freezing issues.
  • Loading branch information
JulianDomingo committed Apr 5, 2024
1 parent b7b2fc9 commit bca6252
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 11 deletions.
24 changes: 13 additions & 11 deletions lib/hls/hls_parser.js
Expand Up @@ -568,7 +568,7 @@ shaka.hls.HlsParser = class {
if (goog.DEBUG) {
const firstSequenceStartTime =
mediaSequenceToStartTime.values().next().value;
goog.asserts.assert(
shaka.log.warning(
firstSequenceStartTime == segment0.startTime,
'Sequence number map is not ordered as expected!');
}
Expand Down Expand Up @@ -605,16 +605,18 @@ shaka.hls.HlsParser = class {
// Drop any earlier references.
const numSegmentsToDrop =
this.minSequenceNumber_ - streamInfo.firstSequenceNumber;
segmentIndex.dropFirstReferences(numSegmentsToDrop);

// Now adjust timestamps back to begin at 0.
const segmentN = segmentIndex.earliestReference();
if (segmentN) {
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);
if (numSegmentsToDrop > 0) {
segmentIndex.dropFirstReferences(numSegmentsToDrop);

// Now adjust timestamps back to begin at 0.
const segmentN = segmentIndex.earliestReference();
if (segmentN) {
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
52 changes: 52 additions & 0 deletions test/hls/hls_live_unit.js
Expand Up @@ -921,6 +921,58 @@ describe('HlsParser live', () => {
manifest.variants[1].video, [ref4]);
});

describe('when ignoreManifestProgramDateTime is set', () => {
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
config.hls.ignoreManifestProgramDateTime = true;

it('does not reset segment times when switching', async () => {
parser.configure(config);

const ref1 = makeReference(
'test:/main.mp4', 0, 2, /* syncTime= */ null);
const ref2 = makeReference(
'test:/main2.mp4', 2, 4, /* syncTime= */ null);

const secondVariant = [
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1",',
'RESOLUTION=1200x940,FRAME-RATE=60\n',
'video2',
].join('');
const masterWithTwoVariants = master + secondVariant;
configureNetEngineForInitialManifest(masterWithTwoVariants,
mediaWithAdditionalSegment, mediaWithAdditionalSegment2);

const manifest = await parser.start('test:/master', playerInterface);
await manifest.variants[0].video.createSegmentIndex();
ManifestParser.verifySegmentIndex(
manifest.variants[0].video, [ref1, ref2]);
expect(manifest.variants[1].video.segmentIndex).toBeNull();

// In the initial playlist, we know the earliest start time is 0, at
// EXT-X-MEDIA-SEQUENCE of 0.
expect(
manifest.variants[0].video.segmentIndex.earliestReference()
.getStartTime())
.toBe(0);

// Update.
fakeNetEngine
.setResponseText('test:/video', mediaWithRemovedSegment)
.setResponseText('test:/video2', mediaWithRemovedSegment2);
await delayForUpdatePeriod();

// Switch. The new variant starts at EXT-X-MEDIA-SEQUENCE of 1.
await manifest.variants[0].video.closeSegmentIndex();
await manifest.variants[1].video.createSegmentIndex();

// The earliest start time of the new segmentIndex should therefore be
// 2.
expect(manifest.variants[0].video.segmentIndex).toBeNull();
const segIdx = manifest.variants[1].video.segmentIndex;
expect(segIdx.earliestReference().getStartTime()).toBe(2);
});
});

it('handles switching during update', async () => {
const ref1 = makeReference(
'test:/main.mp4', 0, 2, /* syncTime= */ null);
Expand Down

0 comments on commit bca6252

Please sign in to comment.