This PR reduces allocation pressure in MP4 segment generation by writing
mdat data directly into the final segment buffer - this avoids one full
media-payload copy per segment
In `H264.getVideoSamples()` `lastVideoSample.data` is initialized with
an empty `Uint8Array` which is always replaced before the sample is
pushed into the `videoSamples` array, so to avoid creating a new empty
placeholder `Uint8Array` for each sample, the empty `Uint8Array` can be
created once and then use to initialize all `lastVideoSample` objects in
that segment.
`TSParser.parsePES_` had a similar placeholder empty `Uint8Array` issue,
which I was able to resolve by creating the PES object in the return
statement instead of upfront, that way it can be initialized with the
final data, avoiding the placeholder.
While yes empty Uint8Arrays definitely need less memory than larger
ones, it is still better to not create 100+ unnecessary objects in rapid
succession which then need to be cleaned up by the garbage collector
later on.
`Mp4Generator.box()` merges all arguments into one `Uint8Array`, so
arrays of `Uint8Arrays` can be spread directly into the
`Mp4Generator.box()` calls instead of doing the extra step of merging
them into a single `Uint8Array` first. I also included a drive-by change
to simplify `array.push(...[x, y])` into `array.push(x, y)` in the
`segmentData` method.
```js
player.addTextTrackAsync("...", "en", "subtitles");
const textTracks = player.getTextTracks();
// Text tracks has a length of 1, but immediately selecting it would fail.
player.selectTextTrack(textTracks[0]);
```
Text track preference runs after we manually selected a text track, in
`src=` we'd wait for `loadeddata` before figuring out which initial text
track should be selected. The fix is to bail out when we already have a
text track active when trying to select an initial text track.
HLS SUPPLEMENTAL-CODECS (e.g. Dolby Vision dvh1) creates duplicate
variant tags with different codecs but the same media playlist URI. Due
to lazy-loading, only one variant gets its drmInfos populated via
createSegmentIndex(). The other variant remains with empty drmInfos and
is treated as unencrypted by getDecodingConfigs_().
In the preferredKeySystems loop, the filter rejected these unencrypted
configs (undefined !== preferredKeySystem), leaving the variant with no
decodingInfo and causing it to be dropped.
Fix: allow unencrypted configs (!keySystem) to pass through, matching
the pattern already used in the second (fallback) loop.
Propagating drmInfos in HLS parser would be more correct but is complex
because stream duplication happens at the tag level with separate cache
keys (URI + codecs) in createStreamInfoFromVariantTags_.
It encapsulates and simplifies the use of requestVideoFrameCallback and
cancelVideoFrameCallback
---------
Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>
Both CTA-5007-A and CTA-5007-B now specifies that the default header
name should be "cta-common-access-token", but it also provides a path
for using a custom header name.
Close https://github.com/shaka-project/shaka-player/issues/9948
## Summary
This PR fixes a demo asset parsing and configuration merge vulnerability
where a
malicious `assetBase64` payload could abuse `__proto__`, `constructor`,
or
`prototype` keys to pollute object prototypes and later reach DOM XSS
gadgets in
Shaka Player Demo.
## Vulnerability details
The vulnerable path was:
1. `demo/main.js` reads attacker-controlled `assetBase64` from the URL
hash.
2. `demo/common/asset.js` copies `extraConfig` into a player config
object with
`for..in`, which allows dangerous magic keys to be applied.
3. `lib/util/config_utils.js` merges config objects with another
`for..in`
traversal and no explicit rejection of `__proto__`, `constructor`, or
`prototype`.
4. The resulting prototype pollution can be turned into DOM XSS when
later demo
UI rebuild paths consume inherited properties.
## Fix approach
This change hardens both the demo entry point and the shared merge
utility:
- `demo/common/asset.js`
- filter dangerous keys when copying `extraConfig`
- restrict `toJSON()` and `fromJSON()` to own properties only
- prevent dangerous keys from being serialized into or restored from
saved demo
assets
- `lib/util/config_utils.js`
- switch config merging from `for..in` to `Object.keys()`
- explicitly reject `__proto__`, `constructor`, and `prototype`
## Regression coverage
Added tests that verify:
- dangerous `extraConfig` keys do not alter the generated player config
- inherited demo asset properties are not serialized into JSON
- dangerous keys are ignored when parsing saved assets back into demo
objects
- inherited magic keys are not traversed during config merges
## Verification
- `python3 build/test.py --quick --filter 'Demo|ConfigUtils' --browsers
ChromeHeadless`
- `python3 build/check.py`
This PR reduces GC pressure during MPEG-TS segment parsing - it replaces
nested PES packet arrays with a flat array plus a PES-start index,
eliminating per-PES inner array allocations and avoiding spread-operator
overhead when concatenating packets - reduces allocations, hence reduces
GC pressure on low-end devices during long livestreams
More types of messages are now supported than before.
SubscribeError handling has been improved.
A new error has been added when no catalog.
Existing messages have been reviewed to eliminate inconsistencies in the
migration from draft-11 to draft-14 support.
Fix log levels
This PR replaces `Array.filter` calls in
`SegmentIndex.merge()`,`mergeAndEvict()`, and `evict()` with more
efficient alternatives. The key addition is `binarySearch` helper: it
repeatedly checks the midpoint and discards half the array each time.
The idea is the same as `Array.findIndex` but exploiting the sorted
order to skip most of the work. `merge()` and `evict()` use this to find
their truncation/expiry boundary; `mergeAndEvict()` uses a simple
forward scan that stops at the first valid reference since stale refs
are always bunched at the front. This is done to reduce iteration during
playback (especially livestream with DVR)
- no big new array creations by default - we don't create one when for
example there is nothing to evict
- fewer comparisons — binary search finds the cutoff without scanning
the whole array
- slice just copies the kept elements and that's it
Add DTS (dts), DTS-HD (dtsh), DTS Digital Surround (dtsc), DTS Express
(dtse), and DTS:X (dtsx) to AUDIO_CODEC_REGEXPS using a single merged
regex /^dts[cehx]?$/.
Include regression tests for all new codec variants.
Add /^mp2v/ regex to VIDEO_CODEC_REGEXPS so that guessCodecsSafe
correctly recognizes MPEG-2 video codec strings. Without this, manifests
containing mp2v codec identifiers would fail codec detection, preventing
playback of MPEG-2 video content.
Includes regression tests verifying mp2v is recognized as a video codec
and not misidentified as audio.
A new integration test has been added.
The use of `stream` within `stream` in `periods.js` has been refactored
to simplify management and avoid duplicates.
---------
Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>
This initial support is complete but not efficient, as it involves
conversion to XML and normal processing. It should only be used for
testing purposes. Improved support will be added in the future.
Tested with https://github.com/Dash-Industry-Forum/dash-json-schema
Note: This is only added to the experimental build.
If the attribute does NOT exist, getAttribute(name) on an element return
an empty string. Empty string '' is not equivalent to 'null'. As a
result 'defaultValue' is not returned, which leads to playback related
issues.
This PR modernizes and optimizes XLink handling in the DASH parser by
removing the legacy flag-based behavior and replacing it with a
standards‑aligned, fast, and deterministic workflow. The changes improve
performance on large MPDs, simplify configuration, and ensure correct
XLink expansion according to DASH/XLink rules.
XLink processing is now automatically enabled only when needed. If the
MPD contains no XLinks, the parser skips processXlinks entirely.
I went ahead and implemented the full structured preference system that
was discussed in
https://github.com/shaka-project/shaka-player/issues/1591.
Instead of just expanding languages to arrays, I replaced all 14
individual preference fields with 3 structured arrays:
```tsx
preferredAudio (language, role, label, channelCount, codec, spatialAudio)
preferredText (language, role, format, forced)
preferredVideo (label, role, codec, hdrLevel, layout)
```
Each array entry works as an AND filter - so you can say things like "I
want Korean with 5.1 surround, but if not available, English is fine
too":
```tsx
player.configure('preferredAudio', [
{language: 'ko', channelCount: 6},
{language: 'ko'},
{language: 'en'},
]);
```
<img width="1728" height="965" alt="image"
src="https://github.com/user-attachments/assets/7b088150-139b-475e-bdba-5bc77dd4e524"
/>
**Config** - Replaced the 14 individual fields with 3 arrays of typed
preference objects (AudioPreference, TextPreference, VideoPreference).
The old fields still work at runtime with a deprecation warning, so
existing apps won't break immediately.
**Demo** - The demo config UI now shows inline expandable preference
lists instead of flat text inputs. You can add/remove entries and
configure each field per entry. URL hash serialization was updated to
use JSON format, with legacy param fallbacks preserved.
So basically, when a license request fails (eg. network Error, server
down whatever), apps can now retry from scratch by calling
`player.retryLicensing()`. This was tricky to implement because of EME
spec limitations: `generateRequest()` can only be called once per
session. So if it fails, it would be stuck.
So I close the old session and create a brand new one with the same
`initData`
> Will Video element throw an error during this process?
we were worried that closing the session would leave the video without
keys for a brief moment, potentially triggering errors. But in practice,
the transition is fast enough( I added a 0.1s delay for CDM clean up)
and the video element handles it gracefully
> Will new encrypted event fire? If not, will it limit this feature?
The encrypted event only fires when the browser first encounters
encrypted content. When we close and recreate a session, the content is
already loaded, so no new event
Solutions: In `CreateSession()` metadata store `initData` and
`initDataType` in the session metadata when the session is first
created. So when `retryLicensing()`is called, we just grab the stored
data and pass it to `generateRequest()` on the new session. No need to
wait for an `encrypted` event at all.
---------
Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>
Tests fixed:
```
UI Customization
✗ big buttons only created when configured [Safari 3.0 (Tizen 3.0)]
Error: Expected 1 to be 0.
at <Jasmine>
at Function.confirmElementMissing (test/test/util/ui_utils.js:70:29 <- test/test/util/ui_utils.js:139:31)
at _callee6$ (test/ui/ui_customization_unit.js:86:13 <- test/ui/ui_customization_unit.js:152:21)
at tryCatch (node_modules/@babel/polyfill/dist/polyfill.js:6473:40)
UI
controls
controls-button-panel
✗ has default elements [Safari 3.0 (Tizen 3.0)]
Error: Expected 1 to be 0.
at <Jasmine>
at Function.confirmElementMissing (test/test/util/ui_utils.js:70:29 <- test/test/util/ui_utils.js:139:31)
at _callee15$ (test/ui/ui_unit.js:425:19 <- test/ui/ui_unit.js:506:27)
at tryCatch (node_modules/@babel/polyfill/dist/polyfill.js:6473:40)
```
This change improves MediaTailor ad handling by:
- Deduplicating cue points to avoid repeated ad markers
- Guarding ad break listener setup to prevent event duplication
- Cleaning up ad state more defensively on stop/end
- Improving static resource caching and tracking robustness
These fixes prevent duplicate events, listener leaks, and inconsistent
ad playback during manifest updates and polling.
---------
Co-authored-by: Theodore Abshire <TheodoreAbshire@Gmail.com>
Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>