The hidden seek button has custom behavior that causes it to attempt to
"delay" touch events on it, to wait to see
if they are a double-tap or not.
This interacted with the double click to fullscreen behavior
unfortunately; a single touch was registered, then it was
registered again 500ms later, causing it to be detected as a double-tap
by the controls container.
This PR changes the hidden seek button to ignore `touchend` events that
happen while the controls are transparent, and changes the initial
`touchend` event on the hidden seek button to not propagate down, thus
preventing the doubling behavior without introducing any lag on the
controls appearing.
Fixes#9705
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>
`airplay` button uses WebKit's proprietary API. In newer versions, this
has been replaced by the RemotePlayback API, which is available in
`remote` button.
More info: https://caniuse.com/mdn-api_remoteplayback
---------
Co-authored-by: Theodore Abshire <TheodoreAbshire@Gmail.com>
- The following buttons are registered: play_pause, mute, fullscreen,
rewind, fast_forward, picture_in_picture, remote, loop, skip_next,
skip_previous
- SmallPlayButton and BigPlayButton are removed
- The following buttons are used by default on mobile: skip_previous,
play_pause, skip_next
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>
The HLS parser used isLive_() in some places, which was equal to
presentationType_ != VOD, and in other places, checked presentationType_
directly.
In fact, isLive_()'s old formulation was checking for all dynamic
content (live & event types), so it should be called isDynamic_(). But
some usages of the old isLive_() actually only cared about live, and
should exclude event types.
This cleans up usage and clarifies each condition to either strictly
live (excluding VOD & event types) or all dynamic content (live & event,
excluding VOD). This is done both inside and outside the HLS parser.
This also makes one behavioral change to HLS event type streams. These
are now shown as "in progress" (dynamic, finite duration) instead of
"live" (dynamic, infinite duration).
---------
Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>