mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 14:16:16 -08:00
fix(playback): preserve Shaka fallback evidence
This commit is contained in:
1 parent
a9b601bee6
commit
50ca78d3a8
9 files changed
+100
-8
No files matched your search
@@ -3,4 +3,4 @@ type: fix
|
||||
area: playback
|
||||
---
|
||||
|
||||
Shaka playback errors now use exact engine evidence, keep recoverable retries from becoming terminal diagnostics, and omit provider URLs, credentials, and response data from technical details.
|
||||
Shaka playback errors now use exact engine evidence, preserve external-player fallback when browser prerequisites are missing, keep recoverable retries non-terminal, and omit provider URLs, credentials, and response data from technical details.
|
||||
@@ -213,7 +213,9 @@ Key files:
|
||||
source kind `shaka`. The Shaka `5.2.2` diagnostic boundary version-locks
|
||||
public severity/category/code evidence, ignores recoverable error events,
|
||||
treats rejected loads as terminal lifecycle outcomes, and never retains or
|
||||
renders raw messages or `error.data`. See the CLAUDE.md "Video Players" feature entry and
|
||||
renders raw messages or `error.data`. A failed browser-support preflight
|
||||
stays unknown but keeps external fallback for clear DASH; KODIPROP DRM still
|
||||
suppresses it. See the CLAUDE.md "Video Players" feature entry and
|
||||
`docs/architecture/m3u-playlist-module.md` ("DASH + ClearKey Playback").
|
||||
- The built-in HTML5/hls.js player is the second guarded consumer.
|
||||
`HtmlVideoPlayerComponent` provides a component-scoped
|
||||
|
||||
@@ -737,7 +737,9 @@ app as a real argument, so it is not an option.
|
||||
`libs/ui/playback/src/lib/shaka-engine/`. Its Shaka `5.2.2` diagnostic
|
||||
boundary version-locks public severity/category/code evidence, ignores
|
||||
recoverable error events, treats rejected loads as terminal lifecycle
|
||||
outcomes, and never retains or renders raw messages or `error.data`; details in
|
||||
outcomes, and never retains or renders raw messages or `error.data`. A failed
|
||||
browser-support preflight stays unknown but keeps external fallback for clear
|
||||
DASH; KODIPROP DRM still suppresses it. Details in
|
||||
`docs/architecture/m3u-playlist-module.md` ("DASH + ClearKey Playback").
|
||||
- External players: MPV, VLC (via IPC to Electron backend)
|
||||
- Embedded MPV (experimental, macOS/Windows/Linux): renders mpv video inside the Electron window through a native addon. macOS uses the libmpv render API in an `NSOpenGLView`; Windows uses in-process libmpv with `--wid` against an app-owned child `HWND`; Linux spawns an out-of-process `mpv --wid=<x11-window>` controlled over a JSON IPC socket (X11/XWayland only, requires system `mpv` on PATH; subtitles/speed/aspect/recording are not exported there). mpv's own screensaver inhibition does not apply to any of these paths, so `EmbeddedMpvNativeService` holds an Electron `powerSaveBlocker` (`prevent-display-sleep`) whenever any session's status is `playing`, and releases it on pause, dispose, or shutdown. Renderer bounds are CSS pixels; the service converts them to native units in the main process (`embedded-mpv-bounds.util.ts`: × page zoom everywhere, × display scale on Windows/Linux whose child windows are positioned in physical pixels; frame-copy bounds stay unscaled), and the session controller re-syncs bounds when `devicePixelRatio` changes. Service: `apps/electron-backend/src/app/services/embedded-mpv-native.service.ts`; full architecture: `docs/architecture/embedded-mpv-native.md`.
|
||||
|
||||
@@ -409,7 +409,17 @@ not prove terminal failure and is ignored. Exact public code/category pairs may
|
||||
classify network, DRM/encryption, manifest/parsing, or media/decode failures.
|
||||
Ambiguous evidence stays `unknown-playback-error`: in particular, the Manifest
|
||||
category alone is not container incompatibility, and Shaka messages never infer
|
||||
CORS, codec, DRM, container, or stage.
|
||||
CORS, codec, DRM, container, or stage. The public critical
|
||||
`STREAMING_ENGINE_STARTUP_INVALID_STATE` code remains exact evidence while its
|
||||
stage and failure stay unknown because the code does not identify a user-facing
|
||||
media cause.
|
||||
|
||||
A failed public `Player.isBrowserSupported()` preflight is not a Shaka error
|
||||
and therefore retains fully unknown technical evidence instead of being
|
||||
mislabelled as an unsupported container. For clear DASH, the diagnostic still
|
||||
offers configured MPV/VLC actions because the failure is specific to the web
|
||||
engine. KODIPROP DRM sources keep external fallback disabled because external
|
||||
players do not receive their key configuration.
|
||||
|
||||
Shaka messages, URLs, headers, request/response bodies, credentials,
|
||||
license/key payloads, and arbitrary `error.data` objects are neither retained
|
||||
|
||||
@@ -1003,6 +1003,11 @@ player in settings.
|
||||
only the documented direct/nested `BAD_HTTP_STATUS` slot may contribute a
|
||||
validated HTTP status. Exact category/code pairs classify failures, while an
|
||||
ambiguous Manifest category or unknown pair remains an unknown diagnostic.
|
||||
The public streaming-startup code `5006` is retained exactly but keeps
|
||||
unknown stage/failure. A failed `Player.isBrowserSupported()` preflight also
|
||||
stays unknown rather than claiming container incompatibility; clear DASH
|
||||
still offers configured external-player actions, while KODIPROP DRM keeps
|
||||
them disabled because those players never receive its keys.
|
||||
Channels with `drm.supported === false` emit a `DrmOrEncryption` diagnostic
|
||||
with a fixed safe detail string and without starting an engine.
|
||||
- HTML5 player: `extension === 'mpd'` branch in `playChannel()`. ArtPlayer:
|
||||
|
||||
@@ -91,6 +91,7 @@ export const SHAKA_ERROR_CODE = {
|
||||
WEBTRANSPORT_INITIALIZATION_FAILED: 4057,
|
||||
DASH_INVALID_JSON: 4061,
|
||||
DASH_UNSUPPORTED_ESSENTIAL_PROPERTY: 4063,
|
||||
STREAMING_ENGINE_STARTUP_INVALID_STATE: 5006,
|
||||
NO_RECOGNIZED_KEY_SYSTEMS: 6000,
|
||||
REQUESTED_KEY_SYSTEM_CONFIG_UNAVAILABLE: 6001,
|
||||
FAILED_TO_CREATE_CDM: 6002,
|
||||
|
||||
@@ -197,6 +197,19 @@ describe('Shaka playback evidence', () => {
|
||||
expect(evidence.httpStatus).toBeUndefined();
|
||||
});
|
||||
|
||||
it('preserves the exact public streaming startup code as unknown failure evidence', () => {
|
||||
expect(
|
||||
createEvidence({ severity: 2, category: 5, code: 5006 }, 'terminal')
|
||||
).toEqual({
|
||||
severity: 'critical',
|
||||
category: 'streaming',
|
||||
engineCode: 5006,
|
||||
disposition: 'terminal',
|
||||
stage: 'unknown',
|
||||
failure: 'unknown',
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: 'manifest parsing',
|
||||
|
||||
@@ -258,6 +258,34 @@ describe('ShakaVideoSession', () => {
|
||||
expect(session.getPlayer()).toBeNull();
|
||||
});
|
||||
|
||||
it('preserves the exact streaming startup error through the session boundary', async () => {
|
||||
const environment = createFakeShakaEnvironment({
|
||||
onCreate: (player) => {
|
||||
player.loadResult = Promise.reject({
|
||||
severity: 2,
|
||||
category: 5,
|
||||
code: 5006,
|
||||
});
|
||||
},
|
||||
});
|
||||
const session = createSession(environment);
|
||||
session.start(video, 'http://example.com/startup-failed.mpd');
|
||||
await flush();
|
||||
|
||||
expect(issues).toHaveLength(1);
|
||||
expect(issues[0].code).toBe(
|
||||
PlaybackDiagnosticCode.UnknownPlaybackError
|
||||
);
|
||||
expect(issues[0].shaka).toEqual({
|
||||
severity: 'critical',
|
||||
category: 'streaming',
|
||||
engineCode: 5006,
|
||||
disposition: 'terminal',
|
||||
stage: 'unknown',
|
||||
failure: 'unknown',
|
||||
});
|
||||
});
|
||||
|
||||
it('emits a critical in-flight error once before teardown interrupts load', async () => {
|
||||
const environment = createFakeShakaEnvironment({
|
||||
onCreate: (player) => {
|
||||
@@ -436,7 +464,7 @@ describe('ShakaVideoSession', () => {
|
||||
expect(player.selectTextTrackCalls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('emits an unsupported-container diagnostic when the browser lacks MSE/EME', async () => {
|
||||
it('keeps external fallback available when the browser cannot run Shaka for clear DASH', async () => {
|
||||
const environment = createFakeShakaEnvironment();
|
||||
environment.browserSupported = false;
|
||||
const session = createSession(environment);
|
||||
@@ -456,6 +484,26 @@ describe('ShakaVideoSession', () => {
|
||||
stage: 'unknown',
|
||||
failure: 'unknown',
|
||||
});
|
||||
expect(issues[0].externalFallbackRecommended).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps external fallback unavailable when browser support fails for KODIPROP DRM', async () => {
|
||||
const environment = createFakeShakaEnvironment();
|
||||
environment.browserSupported = false;
|
||||
const session = createSession(environment);
|
||||
session.start(video, 'http://example.com/encrypted.mpd', {
|
||||
licenseType: 'clearkey',
|
||||
supported: true,
|
||||
clearKeys: { abc: 'def' },
|
||||
});
|
||||
await flush();
|
||||
|
||||
expect(environment.instances).toHaveLength(0);
|
||||
expect(issues).toHaveLength(1);
|
||||
expect(issues[0].code).toBe(
|
||||
PlaybackDiagnosticCode.UnknownPlaybackError
|
||||
);
|
||||
expect(issues[0].externalFallbackRecommended).toBe(false);
|
||||
});
|
||||
|
||||
it('destroy tears down the engine and blocks later starts', async () => {
|
||||
|
||||
@@ -143,7 +143,12 @@ export class ShakaVideoSession {
|
||||
}
|
||||
|
||||
if (!module.Player.isBrowserSupported()) {
|
||||
this.emitTerminalIfCurrent(generation, null, url);
|
||||
this.emitTerminalIfCurrent(
|
||||
generation,
|
||||
null,
|
||||
url,
|
||||
drm === undefined
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,7 +370,8 @@ export class ShakaVideoSession {
|
||||
private emitTerminalIfCurrent(
|
||||
generation: number,
|
||||
error: Partial<ShakaErrorLike> | null,
|
||||
url: string
|
||||
url: string,
|
||||
externalFallbackRecommended?: boolean
|
||||
): void {
|
||||
const issue = classifyShakaPlaybackIssue(
|
||||
error,
|
||||
@@ -373,7 +379,12 @@ export class ShakaVideoSession {
|
||||
ShakaDisposition.Terminal
|
||||
);
|
||||
if (issue) {
|
||||
this.emitIfCurrent(generation, issue);
|
||||
this.emitIfCurrent(
|
||||
generation,
|
||||
externalFallbackRecommended === undefined
|
||||
? issue
|
||||
: { ...issue, externalFallbackRecommended }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user