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
- Play an LL-HLS stream with a separate WebVTT subtitle rendition using
EXT-X-PART.
- Use a
PART-TARGET of 0.8 seconds and a two-second subtitle segment split into 0.8 + 0.8 + 0.4 second parts.
- Select the subtitle track and play at the live edge.
- 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
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
Additional player setup steps
No response
Checklist
Steps to reproduce
EXT-X-PART.PART-TARGETof0.8seconds and a two-second subtitle segment split into0.8 + 0.8 + 0.4second parts.Expected behaviour
Subtitle part loading continues for subsequent segments.
What actually happened?
The subtitle rendition contains this segment. Segment 838 starts at
33.0:SubtitleStreamControllerremains inFRAG_LOADINGand 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,
onSubtitleFragProcessedreturns the controller toIDLEonly when this comparison insrc/controller/subtitle-stream-controller.tscallsfragBufferedComplete:For the second
0.8second part, the same logical end time is calculated in different orders:end >= frag.endis false by one ULP. Although this is the final part,fragBufferedCompleteis not called and the controller remains inFRAG_LOADING.part.startis calculated asfragment.start + fragOffset;frag.endis calculated asfrag.start + frag.durationafter 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.
Console output
The attached full debug log shows that
sn: 838 part: 0returns toIDLE, butsn: 838 part: 1begins downloading and never reachesFRAG_LOADING->IDLE(orBuffered subtitle). The subtitle playlist continues to refresh towardsn: 839, but the subtitle stream controller does not request another subtitle part.novtt5.log
Chrome media internals output