mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(playback): add quality selection to shared player controls (#1470)
* feat(playback): add quality selection to shared player controls Adds a per-session video quality menu (Auto + "1080p"-style levels) to the shared player-controls layer, mirroring the audio-track pattern: - Contract: qualityLevels capability, qualityLevels/qualityAutoEnabled state, setQualityLevel command with AUTO_QUALITY_LEVEL_ID (-1) restoring ABR. - hls.js (HTML5/ArtPlayer via the neutral source bridge): levels with list-index ids, smooth switching through nextLevel, selection read from manualLevel; refresh events extended with MANIFEST_PARSED, LEVELS_UPDATED, LEVEL_SWITCHED. - Shaka (DASH): variant tracks filtered to the active audio language, ABR disabled before selectVariantTrack; manual state keyed to the exact player instance so a session restart never shows a stale selection. - Video.js: new VjsQualityLevels over videojs-contrib-quality-levels (manual = exactly one enabled level, auto = all enabled, derived statelessly). - Embedded MPV and external players report the capability false. The capability derives from the manifest (advertised only for >1 video rendition), nothing persists to Settings, and the menu rides the default-off webPlayerSharedControls rollout gate. Labels come from one shared helper so all engines render the same vocabulary. QUALITY/QUALITY_AUTO keys added to all 19 i18n files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): pin DASH quality candidates to the active audio stream Review findings on #1470: - Shaka quality candidates now match the active variant's exact audioId (language fallback only when Shaka reports none), so a DASH manifest with same-language audio tracks (main vs. commentary, stereo vs. 5.1) can no longer switch the audio track or show duplicate levels when a quality is picked. Regression test added. - Mirror the quality-selection contract into AGENTS.md's Shared Player Controls section, which must stay in sync with CLAUDE.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): track Video.js manual quality intent explicitly Codex re-review finding on #1470: VHS flips a rendition's `enabled` flag off itself when it temporarily excludes failing renditions, so inferring the manual/auto mode from the enabled count could report a manual selection the user never made once exclusions leave a single survivor. VjsQualityLevels now records the picked level object as explicit manual intent: error exclusions read as auto, a picked level that leaves the list reverts to auto, and the bridge resets the intent on every new source. Regression tests added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): re-enable surviving renditions when the picked level is removed Codex follow-up on #1470: dropping manual intent when the picked QualityLevel leaves the list reverted the UI to auto but left the surviving renditions disabled by the earlier manual pick, pinning VHS with no selectable rendition. Reverting to auto now re-enables every remaining level, both on the removal event and lazily from the state read. Regression tests added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
61 files changed
+1582
-10
No files matched your search
@@ -172,8 +172,8 @@ interface PlayerController {
|
||||
### Capabilities
|
||||
|
||||
`PlayerControlsCapabilities` contains booleans for `seek`, `volume`,
|
||||
`audioTracks`, `subtitles`, `playbackSpeed`, `aspectRatio`, `recording`,
|
||||
`pictureInPicture`, `fullscreen`, and `seriesNavigation`.
|
||||
`audioTracks`, `subtitles`, `qualityLevels`, `playbackSpeed`, `aspectRatio`,
|
||||
`recording`, `pictureInPicture`, `fullscreen`, and `seriesNavigation`.
|
||||
|
||||
The default is all-false. An adapter enables only features that its engine and
|
||||
current runtime support. Capability flags primarily control whether optional UI
|
||||
@@ -188,6 +188,7 @@ is rendered; state such as `canSeek`, `canPreviousEpisode`, and
|
||||
- current position, optional duration, live/VOD classification, and seekability;
|
||||
- volume;
|
||||
- pre-labelled audio/subtitle tracks and subtitle-enabled state;
|
||||
- pre-labelled quality levels and the ABR/auto flag;
|
||||
- playback speed and aspect-ratio selections/presets;
|
||||
- recording state;
|
||||
- picture-in-picture active state and runtime availability; and
|
||||
@@ -208,6 +209,7 @@ owner.
|
||||
- `seekTo` / `seekBy`
|
||||
- `setVolume`
|
||||
- `setAudioTrack` / `setSubtitleTrack`
|
||||
- `setQualityLevel` (`AUTO_QUALITY_LEVEL_ID` = `-1` re-enables auto)
|
||||
- `setPlaybackSpeed`
|
||||
- `setAspectRatio`
|
||||
- `toggleRecording`
|
||||
@@ -487,6 +489,64 @@ control chrome. Subtitle rendering in that surface is browser-dependent.
|
||||
AirPlay, Cast, Document Picture-in-Picture, a PiP keyboard shortcut, and an
|
||||
Embedded MPV popup or native mini-window are out of scope.
|
||||
|
||||
### Quality (bitrate/level) selection
|
||||
|
||||
Quality selection is part of the shared controls and therefore rides the same
|
||||
default-off `WEB_PLAYER_SHARED_CONTROLS` rollout. The contract exposes:
|
||||
|
||||
- capability `qualityLevels`;
|
||||
- state `qualityLevels` (pre-labelled options such as "1080p") and
|
||||
`qualityAutoEnabled`; and
|
||||
- command `setQualityLevel(id)`, where `AUTO_QUALITY_LEVEL_ID` (`-1`)
|
||||
re-enables the engine's adaptive (ABR) selection.
|
||||
|
||||
The capability derives from the manifest, not the content type: it is
|
||||
advertised only when the current source exposes **more than one** video
|
||||
rendition, so single-bitrate Xtream VOD files and raw MPEG-TS streams never
|
||||
show the menu, while multi-variant live HLS does. The menu renders next to the
|
||||
audio/subtitle menus with an Auto entry first; Auto is the default, a level
|
||||
reports `selected` only while a manual choice is active, and the choice is
|
||||
per-session — nothing is persisted to Settings.
|
||||
|
||||
Labels come from one shared helper (`quality-level-labels.ts` in
|
||||
`web-video-support/`): frame height first ("1080p"), a 16:9 projection when
|
||||
only the width is known, the bitrate when no dimension is known, and a bitrate
|
||||
suffix only when two levels would otherwise collide on the same label.
|
||||
|
||||
Engine mechanics:
|
||||
|
||||
- **hls.js** (HTML5 and ArtPlayer via the neutral source bridge): levels are
|
||||
`hls.levels` with list-index ids; a manual switch assigns `hls.nextLevel`
|
||||
(switches at the next fragment instead of flushing the buffer), `-1` restores
|
||||
auto, and the selected level is read from the public `manualLevel`. The HLS
|
||||
refresh-event list additionally observes `MANIFEST_PARSED`,
|
||||
`LEVELS_UPDATED`, and `LEVEL_SWITCHED`.
|
||||
- **Shaka (DASH)**: options are variant tracks pinned to the active variant's
|
||||
exact audio stream — variants are audio+video combinations, and picking a
|
||||
quality must not switch the audio track. The filter matches the active
|
||||
variant's `audioId` when Shaka reports one (two same-language audio tracks
|
||||
such as main vs. commentary share a language but never an id) and falls back
|
||||
to the language only when no id is available — sorted by resolution then
|
||||
bandwidth. Manual
|
||||
selection disables ABR via `configure({abr: {enabled: false}})` before
|
||||
`selectVariantTrack(track, true)`; the auto sentinel re-enables ABR. Manual
|
||||
state is keyed to the exact player instance, so a session restart (which
|
||||
creates a fresh player with ABR on) can never render a stale manual
|
||||
selection.
|
||||
- **Video.js**: `VjsQualityLevels` projects the videojs-contrib-quality-levels
|
||||
list (registered by the component's plugin import). VHS has no manual-level
|
||||
setter, so a manual selection enables exactly one level and auto re-enables
|
||||
all. Manual intent is tracked explicitly by the picked level object — VHS
|
||||
also flips `enabled` off for renditions it temporarily excludes after
|
||||
delivery errors, so counting enabled levels would misreport a manual
|
||||
selection. A picked level that leaves the list, a source change, and
|
||||
`clear()` all revert to auto. A missing or throwing plugin degrades to no
|
||||
capability.
|
||||
- **Embedded MPV and external players**: `qualityLevels` stays false —
|
||||
single-program transport streams have no rendition list to offer and no HLS
|
||||
level API is surfaced there. `EmbeddedMpvControlsAdapter.setQualityLevel` is
|
||||
a no-op.
|
||||
|
||||
Native media events refresh the adapter automatically. An engine host must call
|
||||
the public `refresh()` hook after engine-specific getters change
|
||||
without a corresponding media event, including track lists, corrected duration,
|
||||
@@ -705,8 +765,10 @@ The neutral web-video source support shared by HTML5 and ArtPlayer lives in:
|
||||
|
||||
```text
|
||||
libs/ui/playback/src/lib/web-video-support/
|
||||
├── quality-level-labels.ts
|
||||
├── web-video-hls-controls.ts
|
||||
├── web-video-native-text-tracks.ts
|
||||
├── web-video-shaka-controls.ts
|
||||
├── web-video-source-tracks.ts
|
||||
└── web-video-source-controls.bridge.ts
|
||||
```
|
||||
@@ -745,6 +807,7 @@ libs/ui/playback/src/lib/vjs-player/
|
||||
├── vjs-audio-tracks.ts
|
||||
├── vjs-legacy-tracks.ts
|
||||
├── vjs-mpegts-session.ts
|
||||
├── vjs-quality-levels.ts
|
||||
├── vjs-player-controls.bridge.ts
|
||||
├── vjs-player-reset-coordinator.ts
|
||||
├── vjs-player-setup.ts
|
||||
|
||||
Reference in new issue
Block a user