dash.js
Open Source Media Player
Seamless and reliable DASH streaming on any browser-based device
View onGitHub
5507
1728
430.3k
Sponsors
Trusted by the industry leaders
Latest updates from GitHub
Last TTML subtitle in segment not displayed when cue has no explicit end time##### Environment
- [x] The MPD passes the DASH-IF Conformance Tool on https://conformance.dashif.org/
- [x] The stream has correct Access-Control-Allow-Origin headers (CORS)
- [x] There are no network errors such as 404s in the browser console when trying to play the stream
- [x] The issue observed is not mentioned on https://github.com/Dash-Industry-Forum/dash.js/wiki/FAQ
- [x] The issue occurs in the latest reference client on http://reference.dashif.org/dash.js/ and not just on my page
* Link to playable MPD file: N/A (see TTML source below)
* Dash.js version: 5.2.0 (development branch, commit 3292b235)
* Browser name/version: all (logic bug, not browser-specific)
* OS name/version: all
##### Steps to reproduce
1. Create a DASH stream with this TTML as the subtitle track (packaged into fragmented MP4 with `stpp` codec):
```xml
<?xml version="1.0" encoding="utf-8"?>
<tt xmlns="http://www.w3.org/ns/ttml" xmlns:ttp="http://www.w3.org/ns/ttml#parameter"
xmlns:tts="http://www.w3.org/ns/ttml#styling"
xml:lang="en" ttp:timeBase="media" ttp:cellResolution="32 15">
<head>
<styling>
<style xml:id="s0" tts:fontFamily="sansSerif" tts:fontSize="100%" tts:textAlign="center" tts:color="#FFFFFF"/>
<style xml:id="s1" tts:color="#00FF00" tts:backgroundColor="#000000"/>
<style xml:id="s3" tts:color="#FF0000" tts:backgroundColor="#000000"/>
</styling>
<layout>
<region xml:id="r0" tts:origin="10% 80%" tts:extent="80% 15%" tts:displayAlign="before"/>
</layout>
</head>
<body style="s0"><div region="r0">
<p xml:id="sub1" begin="00:00:01.000" end="00:00:04.000">
<span style="s1">Subtitle 1 — has explicit end (4s)</span>
</p>
<p xml:id="sub2" begin="00:00:05.000" end="00:00:08.000">
<span style="s1">Subtitle 2 — has explicit end (8s)</span>
</p>
<p xml:id="sub3" begin="00:00:09.000">
<span style="s3">Subtitle 3 — NO explicit end</span>
</p>
</div></body>
</tt>
```
2. Play the stream with subtitles enabled.
3. Watch for the three subtitles at t=1s, t=5s, and t=9s.
##### Observed behavior
Only the first two subtitles are displayed. The third one (starting at 9s) never appears. No error in the console.
| Cue | begin | end | Result |
|---|---|---|---|
| Subtitle 1 | 1s | 4s | Displayed |
| Subtitle 2 | 5s | 8s | Displayed |
| Subtitle 3 | 9s | *none* | **Never displayed** |
The third cue has no explicit `end` attribute in the TTML source. This is valid TTML but causes the parser to silently drop it.
##### Console output
```
No error — the bug is silent.
```
##### Expected behavior
All three subtitles should be displayed. The third cue should use the segment's end time as its end time.
##### Practical impact
Standard broadcast content (DASH-IF test vectors, BBC Elephant's Dream, HbbTV Reference App) is not affected — their TTML cues all have explicit `end` attributes. The bug requires a `<p>` with no explicit `end`, which is valid TTML but uncommon in professional workflows.Opened by PascalThuet—17 hours ago
parseXml: optimize DASH SegmentTimeline <S> parsing**Is your feature request related to a problem? Please describe.**
Yes.
Large DASH live DVR manifests block the main thread during parsing.
This issue is about the XML parser project [`@svta/cml-xml`](https://github.com/streaming-video-technology-alliance/common-media-library/tree/main/libs/xml), not about changing dash.js itself. dash.js is only the workload used to measure the problem.
On an XL synthetic manifest (50 Periods x 4 AdaptationSets x 500 `S` entries, about 102k XML nodes), the full dash.js manifest parse takes about 40 ms on an M1. The XML parser alone takes about 30.5 ms of that total, or 62%.
About 98% of the nodes in these manifests are `SegmentTimeline` `<S>` entries. They are simple self-closing nodes with only integer attributes like `t`, `d`, `r`, and `k`, but they still go through the full generic parsing path.
**Describe the solution you'd like**
Add a specialized fast path in `parseXml()` for self-closing DASH `SegmentTimeline` `<S>` nodes.
The proposed implementation should happen in [`@svta/cml-xml`](https://github.com/streaming-video-technology-alliance/common-media-library/tree/main/libs/xml), inside `parseXml()`.
**Expected behavior**
- detect `<S ... />` nodes while parsing
- parse `t`, `d`, `r`, and `k` directly as integers
- skip `unescapeHtml()` for numeric attributes
- avoid unnecessary generic attribute parsing work for these nodes
- reuse a shared empty `childNodes` array for self-closing nodes when safe
- preserve the current output shape
In a synthetic benchmark, a specialized eager parser for `<S>` nodes reduced the XML parsing cost from about 39.8 ms to about 19.1 ms on the XL manifest, a reduction of about 52% (`~2.1x` faster).
**Describe alternatives you've considered**
- Lazy parsing of `SegmentTimeline.S`
Rejected. These entries are typically consumed immediately after manifest parsing for duration calculation, segment counting, and segment lookup.
- A local XML parser variant inside dash.js
Rejected. A local clone was slower than the current `@svta/cml-xml` implementation in synthetic benchmarks.
- A downstream dash.js optimization in `DashParser.processNode()`
Useful, but secondary. For example, replacing `arrayNodes.indexOf()` with `Set.has()` helps, but the main bottleneck is still `cmlParseXml()`.
**Additional context**
**Example hot-case nodes**
```xml
<S t="123456" d="180000" />
<S d="180000" r="14" />
<S d="180000" r="-1" k="3" />
```
**Reproduction**
```bash
node test/bench-manifest-parsing.mjs
node test/bench-parsexml.mjs
```
**Real public live DVR example**
- `https://livesim2.dashif.org/livesim2/segtimeline_1/tsbd_21600/testpic_2s/Manifest.mpd`
- verified on March 10, 2026
- `timeShiftBufferDepth="PT6H"`
- about 170 KB
- about 5402 literal `<S>` nodes in the fetched MPD
**Larger multi-period variant**
- `https://livesim2.dashif.org/livesim2/segtimeline_1/tsbd_21600/periods_60/continuous_1/testpic_2s/Manifest.mpd`
- `timeShiftBufferDepth="PT6H"`
- about 901 KB
- 361 periods
- about 5942 literal `<S>` nodes in the fetched MPD
**Why this matters for users**
- on desktop, `39.8 ms -> 19.1 ms` still means about 52% less XML parsing work on the main thread
- on lower-end Smart TV / STB class hardware, that same reduction can translate to roughly 100-200 ms less blocking per manifest refresh
- on live DVR streams, this cost repeats on every manifest update cycle, so users can experience recurring UI hitching rather than a one-time delay
**Environment used for the measurements above**
- dash.js v5.2.0
- [`@svta/cml-xml`](https://github.com/streaming-video-technology-alliance/common-media-library/tree/main/libs/xml) 1.0.1
**What would make this complete**
- no observable output change for current consumers
- measurable improvement on large DASH manifests with dense `SegmentTimeline`
- no regression on non-DASH XML inputs
- benchmark or test coverage proving both correctness and performance
Opened by PascalThuet—5 days ago
DVBSelector: last BaseURL never selected with equal weights (off-by-one in weighted random)##### Environment
- [x] The MPD passes the DASH-IF Conformance Tool on https://conformance.dashif.org/
- [x] The stream has correct Access-Control-Allow-Origin headers (CORS)
- [x] There are no network errors such as 404s in the browser console when trying to play the stream
- [x] The issue observed is not mentioned on https://github.com/Dash-Industry-Forum/dash.js/wiki/FAQ
- [x] The issue occurs in the latest reference client on http://reference.dashif.org/dash.js/ and not just on my page
* Link to playable MPD file: `samples/advanced/mpds/basic-cmcd-config.mpd` (3 BaseURLs, no `dvb:weight`) — remove the `<ContentSteering>` element to expose the bug (Content Steering overrides DVB selection)
* Dash.js version: 5.2.0 (development branch)
* Browser name/version: all (logic bug, not browser-specific)
* OS name/version: all
##### Steps to reproduce
1. Load an MPD with 2+ `<BaseURL>` elements at the same priority, without `dvb:weight` (defaults to 1) and without `<ContentSteering>`
2. Observe which CDN is selected across multiple sessions
> **Note:** `samples/advanced/mpds/basic-cmcd-config.mpd` has 3 BaseURLs without `dvb:weight` but uses Content Steering, which overrides DVB selection and masks the bug.
##### Observed behavior
The last BaseURL in a weighted group is never selected.
`DVBSelector.js` line 114:
```javascript
rn = Math.floor(Math.random() * (totalWeight - 1));
```
`Math.random()` returns `[0, 1)`, so `Math.floor(Math.random() * totalWeight)` already produces `[0, totalWeight-1]`. The extra `- 1` shrinks the range and excludes the last CDN.
**Example : 2 equal-weight CDNs** (most common multi-CDN setup):
`totalWeight = 2`, `cumulWeights = [1, 2]`:
```
rn = Math.floor(Math.random() * 1) = always 0 → always cdn-a, cdn-b never selected
```
##### Console output
```
No error — the bug is silent.
```
##### Expected behavior
Each BaseURL selected with probability proportional to its weight (RFC 2782). With equal weights: ~50/50 for 2 CDNs, ~33/33/33 for 3 CDNs.
Opened by PascalThuet—7 days ago






