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>
- Avoid repeated split() when matching expected root element names
- Improve text content concatenation to reduce string allocations
- Remove unnecessary Array.from() usage when checking text-only children
Widevine's CDM handles renewal automatically, but FairPlay and PlayReady
require manual
`session.update()` calls to renew licenses before they expire.
Previously, developers had to access internal APIs like
`getDrmEngine().activeSessions_` which only works in debug builds - not
ideal for production use.
Based on the discussion in #9505, this PR implements both Option A and
Option C:
**Option A - Manual renewal API:**
```js
player.renewLicense(); // all sessions
player.renewLicense(sessionId); // specific session
```
**Option C - Automatic renewal with config:**
```js
player.configure({
drm: {
renewalIntervalSec: 600
}
});
player.addEventListener('licenserenewal', (event) => {
console.log('License renewed:', event.newSessionMetadata,
event.oldSessionMetadata);
});
```
This way, developers can choose automatic renewal, manual control, or
both depending on their use case.
Under the hood, FairPlay sends a 'renew' message via session.update(),
while PlayReady re-creates the session. Widevine just dispatches the
event since the CDM already handles everything.
Related to #8048
Some platforms (i.e. NOS STB) are not able to play encrypted content
without PSSH boxes in init segments. This PR addresses it by adding PSSH
boxes with actual data if we have it.
Apple QuickTime places a manufacturer field ('appl') immediately after
the handler type ('soun'), causing readTerminatedString() to incorrectly
read 'sounappl' instead of 'soun'. This breaks Opus fMP4 audio playback
for content packaged with Apple tools.
Replace null-terminated string parsing with fixed-length byte reading to
extract exactly 4 bytes for the handler type.
Closes#9576
---------
Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
Added polyfills for `Map.getOrInsert()` and
`Map.getOrInsertComputed()` from the TC39 upsert proposal and refactor
the codebase to use them.
These methods replace the common "check if key exists, then set default"
pattern with a single atomic operation. This improves code readability
and eliminates redundant map lookups throughout the player.
---------
Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
This is the first step in a series of efforts to simplify how we handle
text tracks internally.
The purpose of `autoShowText` has always felt a bit unclear. It was
originally added because Shaka wasn't flexible enough when choosing an
initial text track. I don't think we should try to handle every possible
scenario for initial text track selection. Instead, we should respect
`config.preferredTextLanguage` and let the application decide if it
needs more granular control. Apps can already do this easily with
`getTextTracks()` and `selectTextTrack(track)`.
Ultimately, I'd like to move toward a simpler API where either a text
track is selected or none is. If nothing is selected, we shouldn't
stream any text at all.
See https://github.com/shaka-project/shaka-player/issues/9301 for extra
context.
`removeLatencyFromFirstPacketTime: true` causes time to first byte to be
excluded from throughput measurements, greatly inflating bandwidth
estimates by as much as 2x. This causes elevated rebuffering rates.
Measurements were taken on various Cast models, as well as Chrome on
Linux. In all cases, bandwidth estimates after 60 seconds of 4K Sintel
with the flag set to `true` were about 2x what they would be with the
flag set to `false`.
It appears that time to first byte is excluded from all requests, rather
than just the first segment as is implied by the config docs. It's also
unclear to me, at least, what this was supposed to accomplish, even if
only applied to one segment. For now I'm neither removing nor attempting
to fix the feature, until we can discuss the original thinking behind
it.
As an emergency fix, because the default behavior was so problematic,
this changes the default to `false`. Applications that know better than
me still have the option to set it explicitly to `true` for now.
See issue #9475, which will stay open until we decide what to do with
this flag and its implementation.
In chooseCodecsAndFilterManifest, there was a real lack of comments. So
first and foremost, this adds comments.
Second, group IDs for audio & video streams were made by concatenating
various properties, but without delimiters, so there may have been a
possibility for collisions. This adds delimiters in the group ID
construction.
Third, streams are already sorted by bandwidth before we start grouping
them, so the grouping code for audio & video doesn't need to check
`stream.bandwidth < previousStream.bandwidth`. This should always be
true.
Fourth, the video stream sorting conditions were too nested and hard to
read. This flattens those out in a way that makes them easier to
understand.
Finally, because we're always only allowing one codec per group,
skipping a stream due to `!supportsSmoothSwitch` makes no sense. We're
only ever adding streams to our filter list when their codecs match
what's already in the group. So the check on `supportsSmoothSwitch` is
removed.
This fixes the `active` property on text tracks returned by
`Player#getTextTracks()` still being `true` after calling
`Player#selectTextTrack(null)` when the load mode is `SRC_EQUALS`, this
was also causing the text track selector to still show the text track as
selected after clicking `Off`.
The text track selector UI issue is only a problem on the main branch
(v5.0) as #9048 hasn't been released in any of the release branches yet.
The changes in this PR add support for non-square pixels, e.g. SAR value
of 2:1 (or any other) to the Resolution Selection Menu.
Currently, some contents can be falsely detected as portrait video by
getResolutionLabel_, e.g. a 960x1080p content with a SAR value of 2:1
would be detected as portrait and have its width and height swapped by
getResolutionLabel_. However it is a landscape content: the horizontal
pixels would be stretched to display a horizontal video.