Skip to content

LL-HLS subtitle loading stalls permanently due to a one-ULP rounding difference in the final-part comparison #8051

Description

@SangwonOh

What version of HLS.js are you using?

v1.7.3.

What browser (including version) are you using?

Chrome 152.0.7977.83 (Official Build) (x86_64)

What OS (including version) are you using?

Windows 11

Test stream

This issue may be resolvable without the test stream. If stream is needed, please let me know.

Configuration

{
  lowLatencyMode: true,
  // Our player renders CUES_PARSED events itself. This is not required to reproduce:
  // the issue also reproduces when this is true.
  renderTextTracksNatively: false
}

Additional player setup steps

No response

Checklist

Steps to reproduce

  1. Play an LL-HLS stream with a separate WebVTT subtitle rendition using EXT-X-PART.
  2. Use a PART-TARGET of 0.8 seconds and a two-second subtitle segment split into 0.8 + 0.8 + 0.4 second parts.
  3. Select the subtitle track and play at the live edge.
  4. Within about five seconds, subtitle part requests stop permanently for the session.

Expected behaviour

Subtitle part loading continues for subsequent segments.

What actually happened?

The subtitle rendition contains this segment. Segment 838 starts at 33.0:

#EXT-X-TARGETDURATION:2
#EXT-X-PART-INF:PART-TARGET=0.800000
...
#EXT-X-PROGRAM-DATE-TIME:2026-09-21T08:16:18.867+00:00
#EXT-X-PART:DURATION=0.800000,URI="part_4_838_0_subtitle.vtt",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.800000,URI="part_4_838_1_subtitle.vtt",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.400000,URI="part_4_838_2_subtitle.vtt",INDEPENDENT=YES
#EXTINF:2.000000,
seg_4_838_subtitle.vtt

SubtitleStreamController remains in FRAG_LOADING and stops requesting subtitle parts. In one capture, there were no subtitle part requests for 30 seconds while the subtitle playlist reloaded 49 times.

For parts, onSubtitleFragProcessed returns the controller to IDLE only when this comparison in src/controller/subtitle-stream-controller.ts calls fragBufferedComplete:

const end = start + (part || frag).duration;
if (!part || end >= frag.end) {
  this.fragBufferedComplete(frag, part);
}

For the second 0.8 second part, the same logical end time is calculated in different orders:

// fragment.ts: Part.start is based on the preceding part offset
part.fragOffset = previous.fragOffset + previous.duration; // 0.8
part.start = fragment.start + part.fragOffset;              // 33 + 0.8

// subtitle-stream-controller.ts
const end = part.start + part.duration;                     // 34.599999999999994

// m3u8-parser.ts: Fragment.duration accumulates all part durations
fragment.duration += part.duration;                         // 0.8 + 0.8
fragment.end = fragment.start + fragment.duration;          // 34.6

end >= frag.end is false by one ULP. Although this is the final part, fragBufferedComplete is not called and the controller remains in FRAG_LOADING.

part.start is calculated as fragment.start + fragOffset; frag.end is calculated as frag.start + frag.duration after accumulating part durations.

In our testing, the stall reproduced consistently when the final part ended at a non-integer timestamp, such as 34.6. This makes the two calculation paths more likely to round to adjacent floating-point values.

Main and audio complete buffering from an MSE append event. The subtitle controller uses this time comparison. Non-part fragments short-circuit the comparison with !part, so this affects LL-HLS subtitle renditions.

Tested with a small tolerance

I tested the final-part comparison with a small tolerance and confirmed that subtitle loading continues correctly.

- if (!part || end >= frag.end) {
+ if (!part || end >= frag.end - 1e-6) {

Console output

The attached full debug log shows that sn: 838 part: 0 returns to IDLE, but sn: 838 part: 1 begins downloading and never reaches FRAG_LOADING->IDLE (or Buffered subtitle). The subtitle playlist continues to refresh toward sn: 839, but the subtitle stream controller does not request another subtitle part.

novtt5.log

Chrome media internals output

Activity

  1. added
    Needs TriageIf there is a suspected stream issue, apply this label to triage if it is something we should fix.
    on Sep 21, 2026
  2. added a commit that references this issue on Sep 25, 2026
    995b5c0
  3. removed
    Needs TriageIf there is a suspected stream issue, apply this label to triage if it is something we should fix.
    on Oct 2, 2026
  4. added this to the 1.7.4 milestone on Oct 2, 2026
  5. added 2 commits that reference this issue on Oct 7, 2026
    bc47bec
    89535e2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions