videojs / http-streaming

HLS, DASH, and future HTTP streaming protocols library for video.js
https://videojs-http-streaming.netlify.app/
Other
2.49k stars 420 forks source link

Fix repeated segments #1489

Closed dzianis-dashkevich closed 7 months ago

dzianis-dashkevich commented 7 months ago

The Problem

The player repeats segments sometimes.

To understand better the root cause of the problem, we should understand the following concepts:

A transport stream needs timing information to ensure Video and Audio are shown at the right time and in sync. The most important timecodes are:

The basic unit of a timecode is the Tick. There are 90,000 Ticks in a second (90kHz).

mux.js reads, parses PES packets, and extracts DTS and PTS values. Both values are 33-bit. These timestamps monotonically increase until they hit a 33-bit boundary, which is 8589934592 (2^33). Once they hit 2^33, they roll over to 0, and this process repeats. This "feature" is called timestamp rollover.

Timestamp rollover happens every ~26.5 hours:

mux.js stores the first-encountered DTS value within one timeline and uses it as a reference value to handle rollovers for upcoming timestamps. It adjusts timestamp values based on the direction (backward/forward). Forward adjustment: PTS/DTS value + 2^33. Backward adjustment: PTS/DTS value - 2^33.

Now that we know about PTS, DTS, PCR, and timestampRollover, we need to understand two more concepts: discontinuities(or timelines) and timestamp offsets.

Discontinuities signal that we should reset the decoder since we are switching to different content (e.g., it was encoded Differently, it has different timestamps, etc...). The following examples are common discontinuities cases:

vhs uses the internal term "timeline" since when we hit discontinuity, we switch to a different timeline because PTS/DTS timestamps are completely different from the content before discontinuity.

Now we should understand timestampOffset concept:

The player has a progress bar with a predictable timeline for a user. Segment's PTS and DTS values do not align with the player's time, and they may "jump" during discontinuities. That is why we use timestampOffset to map the segment's timestamps to the time in our player.

So, Segments PTS/DTS + timestampOffset = player's time for a segment.

Ok, now let's put everything together:

Now, the same stream may contain different renditions. Some renditions may have timestamp rollover, while others may not. This means that even if we switch from one rendition to another within the same timeline (no discontinuity), we may still have completely different timestamps. Since we already use timestampOffset adjustment, the easiest and most obvious fix would be re-calculating timestampOffset after each rendition switch.

VHS uses SyncController in order to understand which segment to load after the rendition switch. Sometimes, we don't have enough information to make a proper decision because of the many factors, such as:

Sometimes, the player may select segments already appended to the source buffer. Previously - such appends did not affect the source buffer. Since timestampOffset stays the same, and PTS/DTS values are the same - a browser simply "replaces" already appended data with new data.

Unfortunatelly, re-calculating timestamp offset on every rendition switch makes us less resilient to such appends since now the browser places such segments on top of the buffer (instead of the same place as it was).

This PR is another try to improve the syncing mechanism to avoid selecting already appended data between rendition switches.

Specific Changes proposed

Possible Alternative to avoid using timestampOffsets

Testing Matrix

Type Fast Quality Switch Bandwidth Update Seeking
HLS VOD CMAF OK OK OK
HLS VOD TS OK OK OK
HLS LIVE CMAF OK OK OK
HLS LIVE TS OK OK OK
DASH VOD OK OK OK
DASH LIVE OK OK OK
HLS LL OK OK OK
HLS ByteRange OK OK OK

Requirements Checklist

codecov[bot] commented 7 months ago

Codecov Report

Attention: Patch coverage is 96.72131% with 6 lines in your changes are missing coverage. Please review.

Project coverage is 86.26%. Comparing base (75f7b1a) to head (bad97ec). Report is 1 commits behind head on main.

Files Patch % Lines
src/util/media-sequence-sync.js 96.29% 4 Missing :warning:
src/segment-loader.js 95.91% 2 Missing :warning:
Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #1489 +/- ## ========================================== + Coverage 86.06% 86.26% +0.19% ========================================== Files 42 43 +1 Lines 10752 10877 +125 Branches 2474 2501 +27 ========================================== + Hits 9254 9383 +129 + Misses 1498 1494 -4 ```

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.