mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
fix(playback): structure Shaka diagnostics (#1318)
* docs(playback): design structured Shaka diagnostics * fix(playback): structure Shaka diagnostics * docs(playback): document Shaka evidence boundary * docs(playback): fix Shaka validation commands * fix(playback): preserve Shaka fallback evidence * fix(playback): preserve Shaka text error evidence
This commit is contained in:
1 parent
9a50e7385b
commit
9f4e11d6de
22 files changed
+2779
-277
No files matched your search
@@ -181,7 +181,7 @@ seasons, with per-episode watch-progress bars from playback positions.
|
||||
prefetch is claimed synchronously and answered seasons (including genuinely
|
||||
empty ones) are never re-requested: a failed request leaves `episodes` empty
|
||||
with `isLoading` back to false, which would otherwise re-run the effect that
|
||||
issued it and loop. A *failed* request releases the claim but is pinned to
|
||||
issued it and loop. A _failed_ request releases the claim but is pinned to
|
||||
the episode that triggered it, so a transient portal error retries on the
|
||||
next playback change instead of either looping or giving up permanently.
|
||||
- Gating mirrors ambient mode: the `playerUpNextRail` setting (Settings →
|
||||
@@ -388,6 +388,48 @@ external-player workflows; it is not copied from the HLS error payload into
|
||||
the evidence or technical details. HLS startup development logs are event-only:
|
||||
they do not include provider-supplied channel names or source URLs.
|
||||
|
||||
Shaka Player `5.2.2` errors cross a separate structured boundary before the
|
||||
HTML5 or ArtPlayer DASH session emits a diagnostic. Version-locked tests assert
|
||||
the installed Shaka version plus the public `Severity`, `Category`, and selected
|
||||
online-playback `Code` values used by the boundary. Evidence retains only
|
||||
validated severity/category/code, the lifecycle disposition, an exact
|
||||
code-derived stage and failure kind, and a validated HTTP status. A direct
|
||||
`Network.BAD_HTTP_STATUS` may expose `data[1]` as the status; the same status is
|
||||
accepted from the documented nested networking error for
|
||||
`Drm.LICENSE_REQUEST_FAILED` and
|
||||
`Drm.SERVER_CERTIFICATE_REQUEST_FAILED`. No other `error.data` value is read.
|
||||
|
||||
A recoverable Shaka `error` event does not become a terminal playback
|
||||
diagnostic because the engine continues its retry/recovery lifecycle. A
|
||||
critical event is terminal. A rejected `Player.load()` is also terminal even
|
||||
when its last networking error still carries recoverable severity, because the
|
||||
load lifecycle has ended; the structured evidence preserves both facts as
|
||||
`severity=recoverable` and `disposition=terminal`. Unknown event severity does
|
||||
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. 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. Public DASH text-parser codes are also retained exactly; their
|
||||
`TEXT` category proves the parser subsystem, but not a safe manifest, segment,
|
||||
or media cause, so stage and failure remain unknown.
|
||||
|
||||
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
|
||||
nor rendered. Technical details show only sanitized stage, failure, severity,
|
||||
category, code, disposition, and optional HTTP status. Unsupported playlist
|
||||
DRM uses a fixed safe description rather than echoing provider license
|
||||
configuration.
|
||||
|
||||
`network-error` is reserved for provider/network loading failures. Engines that expose concrete browser security evidence, such as CORS, mixed content, Content Security Policy, or private-network-access blocks, use `browser-access-error` so the UI can explain that the browser player was blocked before playback reached decoding.
|
||||
|
||||
mpegts.js `Early-EOF` failures on MPEG-TS streams are classified as `media-decode-error` instead of generic `network-error`. These failures usually mean the fetch stream ended before mpegts.js expected a complete transport stream, and external players may still handle the same URL more tolerant of short reads or malformed TS boundaries.
|
||||
@@ -397,7 +439,7 @@ with a compact warning badge, a native-player fallback headline, and
|
||||
player-card actions for configured external players. It exposes technical
|
||||
details on demand: diagnostic code, reporting player/source, detected
|
||||
container/MIME, video/audio codecs, native browser error fields, sanitized
|
||||
structured Video.js/VHS and HLS evidence, and existing mpegts details. HLS
|
||||
structured Video.js/VHS, HLS, and Shaka evidence, and existing mpegts details. HLS
|
||||
manifest codec metadata also drives a concise browser-support hint for codecs
|
||||
that Chromium/Electron commonly cannot decode inline, such as HEVC, AC-3,
|
||||
E-AC-3, DTS, and MPEG-2 video.
|
||||
|
||||
@@ -96,22 +96,22 @@ profiles are never mixed into them.
|
||||
|
||||
The benchmark attributes these non-additive intervals:
|
||||
|
||||
| Field | Boundary |
|
||||
| --- | --- |
|
||||
| `dataAcquireMs` | loopback response acquisition |
|
||||
| `m3uParsingMs` | parser call |
|
||||
| `normalizationMs` | parsed-item normalization |
|
||||
| `mainToRendererCloneProxyMs` | sum of normalized import-result delivery plus the upsert and GET main-response-to-preload-success legs |
|
||||
| `storeImportDispatchMs` | renderer import dispatch |
|
||||
| `rendererToMainCloneProxyMs` | sum of the upsert and GET preload-source-to-main-request legs |
|
||||
| `mainToDatabaseWorkerCloneProxyMs` | sum of the upsert and GET main-request-to-worker-receive legs |
|
||||
| `playlistSerializationMs` | database-worker playlist JSON serialization |
|
||||
| `sqliteWriteMs` | SQLite upsert, including its autocommit |
|
||||
| `sqliteReadMs` | SQLite read of the newly persisted playlist |
|
||||
| `playlistDeserializationMs` | database-worker `parseAppPlaylist`, including playlist JSON parsing |
|
||||
| `databaseWorkerToMainCloneProxyMs` | sum of the upsert and GET worker-response-post-to-main-response legs |
|
||||
| `storePublishChannelsMs` | renderer channel publication |
|
||||
| `angularRenderingMs` | publication end to the terminal two-frame paint proof |
|
||||
| Field | Boundary |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| `dataAcquireMs` | loopback response acquisition |
|
||||
| `m3uParsingMs` | parser call |
|
||||
| `normalizationMs` | parsed-item normalization |
|
||||
| `mainToRendererCloneProxyMs` | sum of normalized import-result delivery plus the upsert and GET main-response-to-preload-success legs |
|
||||
| `storeImportDispatchMs` | renderer import dispatch |
|
||||
| `rendererToMainCloneProxyMs` | sum of the upsert and GET preload-source-to-main-request legs |
|
||||
| `mainToDatabaseWorkerCloneProxyMs` | sum of the upsert and GET main-request-to-worker-receive legs |
|
||||
| `playlistSerializationMs` | database-worker playlist JSON serialization |
|
||||
| `sqliteWriteMs` | SQLite upsert, including its autocommit |
|
||||
| `sqliteReadMs` | SQLite read of the newly persisted playlist |
|
||||
| `playlistDeserializationMs` | database-worker `parseAppPlaylist`, including playlist JSON parsing |
|
||||
| `databaseWorkerToMainCloneProxyMs` | sum of the upsert and GET worker-response-post-to-main-response legs |
|
||||
| `storePublishChannelsMs` | renderer channel publication |
|
||||
| `angularRenderingMs` | publication end to the terminal two-frame paint proof |
|
||||
|
||||
`ipcStructuredCloneProxyMs` is the explicit sum of the four directional proxy
|
||||
fields. Each directional field can combine the applicable initial-result,
|
||||
@@ -994,10 +994,24 @@ player in settings.
|
||||
- `ShakaVideoSession` (`libs/ui/playback/src/lib/shaka-engine/`) owns the
|
||||
engine: lazy `import('shaka-player')` on first use (the module is a separate
|
||||
lazy chunk, ~217 KB transfer), `drm.clearKeys` configuration, an operation
|
||||
queue + generation guard against channel-switch races, and Shaka-error →
|
||||
`PlaybackDiagnostic` classification (`PlaybackDiagnosticSource.Shaka`).
|
||||
queue + generation guard against channel-switch races, and a Shaka `5.2.2`
|
||||
public-error boundary. The boundary version-locks its allowlisted
|
||||
severity/category/code values, emits only structured sanitized
|
||||
`PlaybackDiagnosticSource.Shaka` evidence, ignores recoverable error events,
|
||||
and treats a rejected load as terminal even if its final retry error retains
|
||||
recoverable severity. Raw messages and `error.data` never cross the boundary;
|
||||
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. Public DASH text-parser codes likewise retain their
|
||||
exact `TEXT` category/code but keep stage/failure unknown. 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
|
||||
without starting an engine.
|
||||
with a fixed safe detail string and without starting an engine.
|
||||
- HTML5 player: `extension === 'mpd'` branch in `playChannel()`. ArtPlayer:
|
||||
`customType.mpd` in `ArtPlayerSourceSession`. Shared-controls bridge:
|
||||
`WebVideoControlsSource` kind `'shaka'` + `WebVideoShakaControls`
|
||||
|
||||
@@ -0,0 +1,717 @@
|
||||
# Structured Shaka Diagnostics Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace raw Shaka error retention with a version-locked, public,
|
||||
allowlisted evidence boundary that distinguishes recoverable events from
|
||||
terminal load failures.
|
||||
|
||||
**Architecture:** Normalize only Shaka 5.2.2 public severity/category/code
|
||||
values and documented HTTP status layouts into `ShakaPlaybackEvidence`.
|
||||
`ShakaVideoSession` supplies the lifecycle disposition, the classifier maps
|
||||
only exact category/code pairs, and the existing UI renders only structured
|
||||
evidence.
|
||||
|
||||
**Tech Stack:** Angular 21, TypeScript 5.9, Shaka Player 5.2.2, Jest through
|
||||
Nx, Markdown architecture and release-note documentation.
|
||||
|
||||
---
|
||||
|
||||
### Task 0: Establish The Evidence And Baseline
|
||||
|
||||
**Files:**
|
||||
|
||||
- Verify: `package.json`
|
||||
- Verify: `pnpm-lock.yaml`
|
||||
- Verify: `node_modules/shaka-player/lib/util/error.js`
|
||||
- Verify: `node_modules/shaka-player/lib/net/http_plugin_utils.js`
|
||||
- Verify: `node_modules/shaka-player/lib/net/networking_engine.js`
|
||||
- Verify: `node_modules/shaka-player/lib/media/preload_manager.js`
|
||||
- Verify: `node_modules/shaka-player/lib/player.js`
|
||||
- Verify: `node_modules/shaka-player/dist/shaka-player.compiled.d.ts`
|
||||
|
||||
- [x] **Step 1: Install locked dependencies**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
```
|
||||
|
||||
Expected: exit 0, Shaka Player `5.2.2`, and no lockfile change.
|
||||
|
||||
- [x] **Step 2: Verify Nx workspace discovery**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
pnpm nx show projects
|
||||
```
|
||||
|
||||
Expected: exit 0 and output containing `ui-playback`, `web`, and `web-e2e`.
|
||||
|
||||
- [x] **Step 3: Run the affected-project baseline**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
pnpm nx test ui-playback
|
||||
```
|
||||
|
||||
Expected: 86 suites and 801 tests pass before implementation.
|
||||
|
||||
- [x] **Step 4: Audit the installed public runtime**
|
||||
|
||||
Confirm:
|
||||
|
||||
```text
|
||||
shaka.Player.version = v5.2.2
|
||||
Severity = { RECOVERABLE: 1, CRITICAL: 2 }
|
||||
Category = {
|
||||
NETWORK: 1, TEXT: 2, MEDIA: 3, MANIFEST: 4, STREAMING: 5,
|
||||
DRM: 6, PLAYER: 7, CAST: 8, STORAGE: 9, ADS: 10
|
||||
}
|
||||
BAD_HTTP_STATUS data[1] = HTTP status
|
||||
LICENSE_REQUEST_FAILED data[0] = nested Shaka network error
|
||||
SERVER_CERTIFICATE_REQUEST_FAILED data[0] = nested Shaka network error
|
||||
```
|
||||
|
||||
Confirm the installed event order:
|
||||
|
||||
```text
|
||||
recoverable Player#error -> engine continues
|
||||
critical PreloadManager error -> public error event -> load rejection
|
||||
direct manifest/parser throw -> load rejection without required error event
|
||||
attempts exhausted -> final recoverable-severity error rejects load
|
||||
```
|
||||
|
||||
### Task 1: Drive The Public Shaka Evidence Boundary From Failing Tests
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-error-contract.ts`
|
||||
- Create:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-playback-evidence.util.ts`
|
||||
- Create:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-playback-evidence.util.spec.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.model.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-module.types.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing installed-runtime contract test**
|
||||
|
||||
In `shaka-playback-evidence.util.spec.ts`, load the real compiled package in a
|
||||
child process with `global.self = globalThis` and return:
|
||||
|
||||
```typescript
|
||||
interface InstalledShakaContract {
|
||||
readonly version: string;
|
||||
readonly severity: Readonly<Record<string, number>>;
|
||||
readonly category: Readonly<Record<string, number>>;
|
||||
readonly code: Readonly<Record<string, number>>;
|
||||
}
|
||||
```
|
||||
|
||||
Assert:
|
||||
|
||||
```typescript
|
||||
expect(installed.version).toBe(SHAKA_DIAGNOSTIC_VERSION);
|
||||
expect(installed.severity).toEqual(SHAKA_ERROR_SEVERITY);
|
||||
expect(installed.category).toEqual(SHAKA_ERROR_CATEGORY);
|
||||
for (const [name, value] of Object.entries(SHAKA_ERROR_CODE)) {
|
||||
expect(installed.code[name]).toBe(value);
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
NODE_OPTIONS=--experimental-vm-modules node node_modules/jest/bin/jest.js \
|
||||
--config jest.web-esm.workspace.ts --runTestsByPath \
|
||||
libs/ui/playback/src/lib/shaka-engine/shaka-playback-evidence.util.spec.ts \
|
||||
--runInBand
|
||||
```
|
||||
|
||||
Expected: FAIL because the contract and evidence module do not exist.
|
||||
|
||||
- [ ] **Step 2: Add the minimal public model**
|
||||
|
||||
Add const-derived types to `playback-diagnostics.model.ts`:
|
||||
|
||||
```typescript
|
||||
export const ShakaPlaybackSeverity = {
|
||||
Recoverable: 'recoverable',
|
||||
Critical: 'critical',
|
||||
Unknown: 'unknown',
|
||||
} as const;
|
||||
|
||||
export const ShakaPlaybackCategory = {
|
||||
Network: 'network',
|
||||
Text: 'text',
|
||||
Media: 'media',
|
||||
Manifest: 'manifest',
|
||||
Streaming: 'streaming',
|
||||
Drm: 'drm',
|
||||
Player: 'player',
|
||||
Cast: 'cast',
|
||||
Storage: 'storage',
|
||||
Ads: 'ads',
|
||||
Unknown: 'unknown',
|
||||
} as const;
|
||||
|
||||
export const ShakaPlaybackDisposition = {
|
||||
Terminal: 'terminal',
|
||||
Recoverable: 'recoverable',
|
||||
} as const;
|
||||
|
||||
export const ShakaPlaybackStage = {
|
||||
Manifest: 'manifest',
|
||||
Segment: 'segment',
|
||||
Media: 'media',
|
||||
License: 'license',
|
||||
Unknown: 'unknown',
|
||||
} as const;
|
||||
|
||||
export const ShakaPlaybackFailure = {
|
||||
Network: 'network',
|
||||
Drm: 'drm',
|
||||
Manifest: 'manifest',
|
||||
Media: 'media',
|
||||
Unknown: 'unknown',
|
||||
} as const;
|
||||
|
||||
export const ShakaPlaybackUnknownCode = 'unknown' as const;
|
||||
|
||||
export interface ShakaPlaybackEvidence {
|
||||
readonly severity: ShakaPlaybackSeverity;
|
||||
readonly category: ShakaPlaybackCategory;
|
||||
readonly engineCode: number | typeof ShakaPlaybackUnknownCode;
|
||||
readonly disposition: ShakaPlaybackDisposition;
|
||||
readonly stage: ShakaPlaybackStage;
|
||||
readonly failure: ShakaPlaybackFailure;
|
||||
readonly httpStatus?: number;
|
||||
}
|
||||
```
|
||||
|
||||
Add `readonly shaka?: ShakaPlaybackEvidence` to `PlaybackDiagnostic`, and
|
||||
remove `message` from `ShakaErrorLike` so downstream Shaka code has no typed
|
||||
message access:
|
||||
|
||||
```typescript
|
||||
export interface ShakaErrorLike {
|
||||
severity: number;
|
||||
category: number;
|
||||
code: number;
|
||||
data?: readonly unknown[];
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add the exact 5.2.2 public allowlist**
|
||||
|
||||
In `shaka-error-contract.ts`, export:
|
||||
|
||||
```typescript
|
||||
export const SHAKA_DIAGNOSTIC_VERSION = 'v5.2.2';
|
||||
export const SHAKA_ERROR_SEVERITY = {
|
||||
RECOVERABLE: 1,
|
||||
CRITICAL: 2,
|
||||
} as const;
|
||||
export const SHAKA_ERROR_CATEGORY = {
|
||||
NETWORK: 1,
|
||||
TEXT: 2,
|
||||
MEDIA: 3,
|
||||
MANIFEST: 4,
|
||||
STREAMING: 5,
|
||||
DRM: 6,
|
||||
PLAYER: 7,
|
||||
CAST: 8,
|
||||
STORAGE: 9,
|
||||
ADS: 10,
|
||||
} as const;
|
||||
```
|
||||
|
||||
Define `SHAKA_ERROR_CODE` with the exact active public Shaka 5.2.2 online
|
||||
playback codes from categories NETWORK through PLAYER, including the direct
|
||||
and nested HTTP codes, media and manifest codes, all DRM codes, and
|
||||
`LOAD_INTERRUPTED`. Do not include retired numeric gaps. The contract test
|
||||
must verify every name/value against the installed runtime.
|
||||
|
||||
- [ ] **Step 4: Write failing sanitizer and privacy tests**
|
||||
|
||||
Use the documented direct bad-status shape:
|
||||
|
||||
```typescript
|
||||
const raw = {
|
||||
severity: 1,
|
||||
category: 1,
|
||||
code: 1001,
|
||||
message: 'https://user:secret@provider.example/manifest.mpd',
|
||||
data: [
|
||||
'https://provider.example/manifest.mpd?token=secret',
|
||||
503,
|
||||
'provider body secret',
|
||||
{ Authorization: 'Bearer secret' },
|
||||
0,
|
||||
'https://provider.example/final?token=secret',
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Expect:
|
||||
|
||||
```typescript
|
||||
{
|
||||
severity: 'recoverable',
|
||||
category: 'network',
|
||||
engineCode: 1001,
|
||||
disposition: 'terminal',
|
||||
stage: 'unknown',
|
||||
failure: 'network',
|
||||
httpStatus: 503,
|
||||
}
|
||||
```
|
||||
|
||||
Add a nested `DRM + LICENSE_REQUEST_FAILED` shape whose `data[0]` is the same
|
||||
network error and whose remaining data contains license/session secrets.
|
||||
Expect only the nested status number to survive.
|
||||
|
||||
Cover:
|
||||
|
||||
- HTTP boundaries 99/100/599/600 and non-integers;
|
||||
- status-like values on non-documented codes remain absent;
|
||||
- unknown severity/category/code values become `unknown`;
|
||||
- mismatched public category/code pairs have `failure=unknown`;
|
||||
- no URL, message, header, body, key, license, credential, or arbitrary
|
||||
property survives `JSON.stringify(evidence)`;
|
||||
- exact manifest, segment, media, license, and unknown stages;
|
||||
- exact network, DRM, manifest, media, restrictions, and unknown failures.
|
||||
|
||||
- [ ] **Step 5: Implement the minimal sanitizer**
|
||||
|
||||
In `shaka-playback-evidence.util.ts`, expose:
|
||||
|
||||
```typescript
|
||||
export function createShakaPlaybackEvidence(
|
||||
error: Partial<ShakaErrorLike> | null | undefined,
|
||||
disposition: ShakaPlaybackDisposition
|
||||
): ShakaPlaybackEvidence;
|
||||
```
|
||||
|
||||
Use only:
|
||||
|
||||
```typescript
|
||||
error?.severity;
|
||||
error?.category;
|
||||
error?.code;
|
||||
error?.data?.[1]; // exact direct BAD_HTTP_STATUS only
|
||||
error?.data?.[0]; // exact documented nested DRM network error only
|
||||
```
|
||||
|
||||
Build severity/category/code through exact sets, derive stage/failure through
|
||||
exact category/code pairs, and return a fresh flat evidence object. Do not
|
||||
read or serialize any other field.
|
||||
|
||||
Run the focused spec again.
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 2: Drive Exact Classification And Shared Diagnostic Retention
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.spec.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.ts`
|
||||
|
||||
- [ ] **Step 1: Replace heuristic expectations with failing exact-code tests**
|
||||
|
||||
Change the classifier API to:
|
||||
|
||||
```typescript
|
||||
classifyShakaPlaybackIssue(
|
||||
error,
|
||||
metadata,
|
||||
disposition
|
||||
): PlaybackDiagnostic | null;
|
||||
```
|
||||
|
||||
Add expectations:
|
||||
|
||||
```text
|
||||
NETWORK + BAD_HTTP_STATUS -> network-error
|
||||
DRM + REQUESTED_KEY_SYSTEM_CONFIG_UNAVAILABLE -> drm-or-encryption
|
||||
MANIFEST + DASH_NO_COMMON_KEY_SYSTEM -> drm-or-encryption
|
||||
MANIFEST + DASH_INVALID_XML -> unknown-playback-error
|
||||
MANIFEST + DASH_UNSUPPORTED_CONTAINER -> unsupported-container
|
||||
MANIFEST + CONTENT_UNSUPPORTED_BY_BROWSER -> unknown-playback-error
|
||||
MEDIA + MEDIA_SOURCE_OPERATION_FAILED -> media-decode-error
|
||||
MANIFEST + RESTRICTIONS_CANNOT_BE_MET -> unknown-playback-error
|
||||
mismatched category/code -> unknown-playback-error
|
||||
unknown values -> unknown-playback-error
|
||||
recoverable disposition -> null
|
||||
```
|
||||
|
||||
Put misleading `message` and `data` strings such as `CORS codec license`
|
||||
alongside unknown or mismatched structured values and prove they do not change
|
||||
classification.
|
||||
|
||||
Run the focused classifier spec.
|
||||
|
||||
Expected: FAIL against the current category/message heuristic classifier.
|
||||
|
||||
- [ ] **Step 2: Implement exact classification**
|
||||
|
||||
Create evidence first:
|
||||
|
||||
```typescript
|
||||
const evidence = createShakaPlaybackEvidence(error, disposition);
|
||||
if (evidence.disposition === ShakaPlaybackDisposition.Recoverable) {
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
Select the top-level code only from exact evidence:
|
||||
|
||||
```typescript
|
||||
network -> NetworkError
|
||||
drm -> DrmOrEncryption
|
||||
DASH_UNSUPPORTED_CONTAINER -> UnsupportedContainer
|
||||
MEDIA_SOURCE_OPERATION_FAILED -> MediaDecodeError
|
||||
MEDIA_SOURCE_OPERATION_THREW -> MediaDecodeError
|
||||
VIDEO_ERROR -> MediaDecodeError
|
||||
everything else -> UnknownPlaybackError
|
||||
```
|
||||
|
||||
Call `createPlaybackDiagnostic` with `httpStatus` and `shaka`, but no raw
|
||||
`details` or native message.
|
||||
|
||||
Extend `createPlaybackDiagnostic` to accept and retain:
|
||||
|
||||
```typescript
|
||||
readonly shaka?: ShakaPlaybackEvidence;
|
||||
```
|
||||
|
||||
Run the focused evidence and classifier specs.
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 3: Sanitize the pre-engine DRM diagnostic**
|
||||
|
||||
Change `createUnsupportedDrmDiagnostic` to ignore the provider-supplied
|
||||
license string in technical details:
|
||||
|
||||
```typescript
|
||||
details: 'Unsupported DRM license configuration';
|
||||
```
|
||||
|
||||
Add a secret-bearing license string test and prove the diagnostic JSON and
|
||||
details omit it while the top-level code remains `drm-or-encryption` and
|
||||
external fallback remains disabled.
|
||||
|
||||
### Task 3: Drive Session Lifecycle And Adapter Routing
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-video-session.spec.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-video-session.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/shaka-engine/shaka-player-test-double.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/art-player/art-player-source-session.dash.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Write failing recoverable/terminal routing tests**
|
||||
|
||||
Add session regressions for:
|
||||
|
||||
```text
|
||||
recoverable Player#error -> no diagnostic, same current player
|
||||
unknown-severity Player#error -> no diagnostic, same current player
|
||||
critical Player#error -> one terminal diagnostic, player destroyed
|
||||
recoverable-severity load rejection -> one terminal diagnostic
|
||||
critical event during stalled load -> one diagnostic, later interruption ignored
|
||||
module-loader Error with URL/token -> terminal unknown evidence without message
|
||||
```
|
||||
|
||||
For the terminal rejected `BAD_HTTP_STATUS`, expect:
|
||||
|
||||
```typescript
|
||||
expect(issue.shaka).toEqual({
|
||||
severity: 'recoverable',
|
||||
category: 'network',
|
||||
engineCode: 1001,
|
||||
disposition: 'terminal',
|
||||
stage: 'unknown',
|
||||
failure: 'network',
|
||||
httpStatus: 503,
|
||||
});
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
NODE_OPTIONS=--experimental-vm-modules node node_modules/jest/bin/jest.js \
|
||||
--config jest.web-esm.workspace.ts --runTestsByPath \
|
||||
libs/ui/playback/src/lib/shaka-engine/shaka-video-session.spec.ts \
|
||||
--runInBand
|
||||
```
|
||||
|
||||
Expected: FAIL because the current session suppresses only event severity,
|
||||
passes arbitrary messages on rejection, and has no structured disposition.
|
||||
|
||||
- [ ] **Step 2: Implement route-owned disposition**
|
||||
|
||||
In `ShakaVideoSession`:
|
||||
|
||||
- call the classifier with `Terminal` for module rejection, unsupported
|
||||
browser support, and `load()` rejection;
|
||||
- call it with `Recoverable` for exact recoverable events and emit nothing
|
||||
when it returns null;
|
||||
- call it with `Terminal` only for exact critical events;
|
||||
- ignore unknown-severity events;
|
||||
- suppress interruption only for the exact
|
||||
`CRITICAL + PLAYER + LOAD_INTERRUPTED` triple;
|
||||
- remove `toErrorMessage` and every Shaka message fallback;
|
||||
- tear down only after a non-null terminal diagnostic.
|
||||
|
||||
Run the focused session spec.
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 3: Write and pass the ArtPlayer adapter regression**
|
||||
|
||||
Configure the fake Shaka player's load promise to reject with a documented
|
||||
bad-status shape, pass `emitPlaybackIssue` into
|
||||
`ArtPlayerSourceSession`, invoke the `mpd` custom type, and expect one
|
||||
diagnostic with:
|
||||
|
||||
```typescript
|
||||
{
|
||||
source: PlaybackDiagnosticSource.Shaka,
|
||||
player: InlinePlaybackPlayer.ArtPlayer,
|
||||
code: PlaybackDiagnosticCode.NetworkError,
|
||||
httpStatus: 503,
|
||||
shaka: expect.objectContaining({
|
||||
engineCode: 1001,
|
||||
disposition: 'terminal',
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
Prove the response body/header/token sentinel is absent from serialized
|
||||
diagnostic data other than the pre-existing active `sourceUrl`.
|
||||
|
||||
### Task 4: Drive Safe Rendered Technical Details
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/web-player-view/web-player-view-diagnostics.utils.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing rendered-detail regression**
|
||||
|
||||
Create a Shaka diagnostic with safe evidence plus malicious legacy fields:
|
||||
|
||||
```typescript
|
||||
details: 'Authorization: Bearer shaka-render-secret',
|
||||
nativeErrorMessage:
|
||||
'https://provider.example/license?token=shaka-render-secret',
|
||||
shaka: {
|
||||
severity: 'recoverable',
|
||||
category: 'network',
|
||||
engineCode: 1001,
|
||||
disposition: 'terminal',
|
||||
stage: 'unknown',
|
||||
failure: 'network',
|
||||
httpStatus: 503,
|
||||
},
|
||||
```
|
||||
|
||||
Expect the error-details row to equal:
|
||||
|
||||
```text
|
||||
stage=unknown · failure=network · severity=recoverable · category=network · code=1001 · disposition=terminal · HTTP 503
|
||||
```
|
||||
|
||||
Prove the rendered values omit the secret, provider hostname,
|
||||
`Authorization`, raw body text, and legacy details.
|
||||
|
||||
Run the focused component spec.
|
||||
|
||||
Expected: FAIL because the UI has no Shaka structured branch.
|
||||
|
||||
- [ ] **Step 2: Render only Shaka evidence**
|
||||
|
||||
Add the Shaka branch before the HLS/VHS/legacy branches:
|
||||
|
||||
```typescript
|
||||
if (issue.shaka) {
|
||||
return [
|
||||
`stage=${issue.shaka.stage}`,
|
||||
`failure=${issue.shaka.failure}`,
|
||||
`severity=${issue.shaka.severity}`,
|
||||
`category=${issue.shaka.category}`,
|
||||
`code=${issue.shaka.engineCode}`,
|
||||
`disposition=${issue.shaka.disposition}`,
|
||||
issue.shaka.httpStatus === undefined
|
||||
? ''
|
||||
: `HTTP ${issue.shaka.httpStatus}`,
|
||||
]
|
||||
.filter((value) => value.length > 0)
|
||||
.join(' · ');
|
||||
}
|
||||
```
|
||||
|
||||
Run the focused component spec.
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 5: Documentation, Release Note, And Full Validation
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/architecture/embedded-inline-playback.md`
|
||||
- Modify: `docs/architecture/m3u-playlist-module.md`
|
||||
- Modify: `CLAUDE.md`
|
||||
- Modify: `AGENTS.md`
|
||||
- Create: `.changes/playback-structured-shaka-diagnostics.md`
|
||||
|
||||
- [ ] **Step 1: Update canonical documentation**
|
||||
|
||||
Document:
|
||||
|
||||
- Shaka 5.2.2 public version lock;
|
||||
- evidence fields and rejected raw fields;
|
||||
- direct and nested documented HTTP status layouts;
|
||||
- exact failure/stage classification and ambiguous unknown cases;
|
||||
- recoverable event versus terminal load-rejection semantics;
|
||||
- sanitized technical detail output;
|
||||
- unchanged source routing, ClearKey fallback rule, and VOD failover.
|
||||
|
||||
Keep the high-level Shaka summaries in `CLAUDE.md` and `AGENTS.md`
|
||||
consistent.
|
||||
|
||||
- [ ] **Step 2: Add the user-facing release note**
|
||||
|
||||
Create:
|
||||
|
||||
```markdown
|
||||
---
|
||||
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.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run focused and affected validation**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
NODE_OPTIONS=--experimental-vm-modules node node_modules/jest/bin/jest.js \
|
||||
--config jest.web-esm.workspace.ts --runTestsByPath \
|
||||
libs/ui/playback/src/lib/shaka-engine/shaka-playback-evidence.util.spec.ts \
|
||||
libs/ui/playback/src/lib/shaka-engine/shaka-error-classifier.spec.ts \
|
||||
libs/ui/playback/src/lib/shaka-engine/shaka-video-session.spec.ts \
|
||||
libs/ui/playback/src/lib/art-player/art-player-source-session.dash.spec.ts \
|
||||
libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts \
|
||||
--runInBand
|
||||
pnpm nx test ui-playback
|
||||
pnpm nx lint ui-playback
|
||||
pnpm nx typecheck web
|
||||
pnpm run i18n:check
|
||||
pnpm run release:notes:validate
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Expected: every command exits 0.
|
||||
|
||||
- [ ] **Step 4: Complete the test-impact pass**
|
||||
|
||||
Confirm:
|
||||
|
||||
```text
|
||||
Affected project: ui-playback
|
||||
Unit/component coverage: required and run
|
||||
Lint: required and run
|
||||
Web typecheck: required and run
|
||||
i18n validation: required and run
|
||||
Release-note validation: required and run
|
||||
E2E: not required; no workflow, routing, player selection, or integration
|
||||
lifecycle changed, and event/rejection behavior is covered at the
|
||||
session plus ArtPlayer adapter boundaries
|
||||
```
|
||||
|
||||
### Task 6: Independent Review, Fixes, And Ready PR
|
||||
|
||||
**Files:**
|
||||
|
||||
- Review: complete diff from `origin/master` to branch HEAD
|
||||
|
||||
- [ ] **Step 1: Commit the implementation before review**
|
||||
|
||||
Create focused conventional commits for the evidence boundary/runtime change
|
||||
and documentation/release note. Confirm:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git log --oneline origin/master..HEAD
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Dispatch an independent local Codex review**
|
||||
|
||||
Give the reviewer:
|
||||
|
||||
```text
|
||||
Base: origin/master
|
||||
Head: branch HEAD
|
||||
Requirements: the structured Shaka design and this implementation plan
|
||||
Focus: actionable P0/P1/P2 correctness, privacy, public-contract drift,
|
||||
lifecycle duplication/suppression, classification accuracy, and tests
|
||||
```
|
||||
|
||||
Require a full-diff review. If the reviewer reports an issue, verify it
|
||||
against the installed Shaka 5.2.2 source before changing code.
|
||||
|
||||
- [ ] **Step 3: Fix confirmed findings through TDD**
|
||||
|
||||
For every confirmed P0/P1/P2:
|
||||
|
||||
1. add or tighten a regression test;
|
||||
2. run it and observe the expected failure;
|
||||
3. implement the minimal fix;
|
||||
4. rerun the focused test and affected suite;
|
||||
5. commit the fix.
|
||||
|
||||
- [ ] **Step 4: Repeat independent review**
|
||||
|
||||
Review the updated full diff from `origin/master`. Expected: no actionable
|
||||
P0/P1/P2 findings.
|
||||
|
||||
- [ ] **Step 5: Repeat the complete validation ladder**
|
||||
|
||||
Rerun every command from Task 5 Step 3 after the final review fix. Expected:
|
||||
all exit 0 with fresh output.
|
||||
|
||||
- [ ] **Step 6: Push and create a ready PR**
|
||||
|
||||
Push `agent/structured-shaka-diagnostics` and create a non-draft PR with:
|
||||
|
||||
```text
|
||||
Title: fix(playback): structure Shaka diagnostics
|
||||
Base: master
|
||||
Summary: exact Shaka 5.2.2 evidence, lifecycle-aware recoverable handling,
|
||||
safe technical details
|
||||
Testing: list every fresh validation command and result
|
||||
```
|
||||
|
||||
Inspect the created PR and confirm it is ready for review.
|
||||
@@ -0,0 +1,286 @@
|
||||
# Structured Shaka Diagnostics
|
||||
|
||||
## Context
|
||||
|
||||
PR #1314 stopped treating an ambiguous native `MediaError` as codec evidence
|
||||
and retained explicit HTTP evidence. PR #1316 added an allowlisted hls.js
|
||||
boundary for HTML5 and ArtPlayer. PR #1317 added an allowlisted Video.js/VHS
|
||||
boundary for the default player. Shaka remains the only web source engine that
|
||||
copies an arbitrary message and serialized `error.data` into a diagnostic.
|
||||
|
||||
The locked workspace installs Shaka Player `5.2.2`. The audit used the
|
||||
installed package's public declarations, `shaka.util.Error` JSDoc, compiled
|
||||
runtime exports, and the load/error paths in the installed source. The public
|
||||
runtime reports:
|
||||
|
||||
- `shaka.Player.version === "v5.2.2"`;
|
||||
- severity values `RECOVERABLE=1` and `CRITICAL=2`;
|
||||
- category values `NETWORK=1`, `TEXT=2`, `MEDIA=3`, `MANIFEST=4`,
|
||||
`STREAMING=5`, `DRM=6`, `PLAYER=7`, `CAST=8`, `STORAGE=9`, and `ADS=10`;
|
||||
- numeric `shaka.util.Error.Code` values documented by the installed public
|
||||
API.
|
||||
|
||||
The existing `ShakaVideoSession` binds the public `Player#error` event before
|
||||
calling `load()`, and also observes the public `load()` rejection. Those paths
|
||||
have different terminal meaning and must not be collapsed into a
|
||||
severity-only gate.
|
||||
|
||||
## Goals
|
||||
|
||||
- Convert Shaka errors into a minimal allowlisted `ShakaPlaybackEvidence`
|
||||
value before classification or UI rendering.
|
||||
- Retain only validated public severity, category, and code values, an
|
||||
explicit lifecycle disposition, a code-proven stage/failure kind, and a
|
||||
documented HTTP status.
|
||||
- Classify only exact Shaka 5.2.2 category/code pairs.
|
||||
- Keep insufficient or inconsistent evidence unknown.
|
||||
- Keep recoverable player events from becoming terminal IPTVnator
|
||||
diagnostics.
|
||||
- Treat a rejected `Player.load()` as terminal even when the final public
|
||||
Shaka error still carries `RECOVERABLE`.
|
||||
- Prevent messages, URLs, headers, bodies, license/key payloads, credentials,
|
||||
and arbitrary objects from reaching stored or rendered diagnostics.
|
||||
- Version-lock the allowlist and tests to the installed Shaka public API.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing hls.js or Video.js/VHS evidence contracts.
|
||||
- Redesigning mpegts.js diagnostics.
|
||||
- Adding top-level diagnostic codes, history, persistence, correlation,
|
||||
probes, automatic engine failover, or a cross-player recommendation matrix.
|
||||
- Inspecting Shaka private loaders, networking state, parser internals, or
|
||||
retry counters.
|
||||
- Changing VOD multi-source auto-failover.
|
||||
- Inferring CORS, codec, DRM, container, or stage from message text.
|
||||
|
||||
## Approaches Considered
|
||||
|
||||
### Public error boundary with lifecycle disposition (selected)
|
||||
|
||||
Normalize the public `severity`, `category`, `code`, and only the documented
|
||||
HTTP-status slot into `ShakaPlaybackEvidence`. The session supplies
|
||||
`recoverable` for a recoverable event and `terminal` for a critical event or a
|
||||
rejected load. Classification and technical details accept only the sanitized
|
||||
evidence.
|
||||
|
||||
This preserves the strongest stable facts while making unsafe fields
|
||||
structurally unavailable to downstream code. It also represents the important
|
||||
case where Shaka's final network error remains severity-recoverable but the
|
||||
load promise has terminated.
|
||||
|
||||
### Severity-only event and rejection gate
|
||||
|
||||
Suppressing every `severity=RECOVERABLE` error would hide a terminal manifest
|
||||
load failure after Shaka exhausts retries. Treating every error as terminal
|
||||
would tear down a player that Shaka is still recovering. The route that
|
||||
delivers the error is therefore required evidence.
|
||||
|
||||
### Inspect request/parser internals
|
||||
|
||||
Private networking and parser state could reveal request types and richer
|
||||
stages, but it also exposes URLs, headers, response data, license payloads, and
|
||||
unstable implementation details. This is rejected by the privacy and public
|
||||
contract requirements.
|
||||
|
||||
### Stop after the audit
|
||||
|
||||
Stopping would be correct if the public API could not distinguish safe facts
|
||||
or terminal lifecycle. The installed API exposes exact enums, documented
|
||||
status layouts, and distinct event/rejection semantics, so a stable focused
|
||||
improvement is available.
|
||||
|
||||
## Installed Runtime And Event Ordering
|
||||
|
||||
Shaka 5.2.2 documents recoverable severity as an error from which the player
|
||||
is attempting to recover. It explicitly warns that some media-segment retry
|
||||
paths may never escalate to a critical error.
|
||||
|
||||
The installed runtime orders errors as follows:
|
||||
|
||||
1. `PreloadManager.onError()` rejects its success promise for a critical
|
||||
error, destroys preload work, and synchronously dispatches the public
|
||||
`error` event.
|
||||
2. The existing session's event listener therefore observes that critical
|
||||
event before the `await player.load()` continuation handles the rejection.
|
||||
3. Session teardown changes the current-player identity; the later rejection
|
||||
is ignored, preventing a duplicate diagnostic.
|
||||
4. Direct manifest/parser failures can reject the preload/load promise without
|
||||
dispatching a player error. The load-rejection path must classify them.
|
||||
5. The networking engine retries errors while their public severity is
|
||||
recoverable. When attempts are exhausted it throws the last error without
|
||||
rewriting its severity. A rejected `load()` is therefore terminal by
|
||||
lifecycle even if evidence says `severity=recoverable`.
|
||||
6. A recoverable public player event is not terminal and must leave the
|
||||
current engine attached.
|
||||
|
||||
`LOAD_INTERRUPTED` is suppressed only when the exact public triple is
|
||||
`CRITICAL + PLAYER + LOAD_INTERRUPTED`; an arbitrary object containing the
|
||||
number `7000` is not enough.
|
||||
|
||||
## Version-locked Public Contract
|
||||
|
||||
The production allowlist contains the exact Shaka 5.2.2 severity/category
|
||||
values and the public online-playback error codes used by the boundary. A
|
||||
contract test loads the installed compiled package in a child Node process,
|
||||
asserts `v5.2.2`, compares the full severity/category maps, and verifies every
|
||||
allowlisted code name/value against `shaka.util.Error.Code`.
|
||||
|
||||
The explicit version assertion makes a dependency upgrade fail before new or
|
||||
changed codes are silently accepted. An upgrade requires reviewing the new
|
||||
public JSDoc layouts and lifecycle before updating the lock.
|
||||
|
||||
## Evidence Contract
|
||||
|
||||
`ShakaPlaybackEvidence` contains:
|
||||
|
||||
- `severity`: `recoverable`, `critical`, or `unknown`;
|
||||
- `category`: one validated public category name in lowercase, otherwise
|
||||
`unknown`;
|
||||
- `engineCode`: one allowlisted public numeric code, otherwise `unknown`;
|
||||
- `disposition`: `terminal` or `recoverable`, supplied by the session route;
|
||||
- `stage`: `manifest`, `segment`, `media`, `license`, or `unknown`;
|
||||
- `failure`: `network`, `drm`, `manifest`, `media`, or `unknown`;
|
||||
- optional `httpStatus`: an integer from 100 through 599.
|
||||
|
||||
The boundary does not retain:
|
||||
|
||||
- `error.message`;
|
||||
- arbitrary or serialized `error.data`;
|
||||
- request, redirect, manifest, segment, license, or certificate URLs;
|
||||
- request or response headers;
|
||||
- response text, bodies, or binary data;
|
||||
- browser exceptions and events;
|
||||
- DRM session metadata, keys, licenses, provider payloads, or credentials;
|
||||
- unknown object properties.
|
||||
|
||||
The existing `PlaybackDiagnostic.sourceUrl` remains the active source metadata
|
||||
used by Retry, Copy URL, explicit external-player actions, and the existing VOD
|
||||
failover flow. No URL is copied from a Shaka error.
|
||||
|
||||
## HTTP Status Extraction
|
||||
|
||||
Shaka 5.2.2 publicly documents:
|
||||
|
||||
- `BAD_HTTP_STATUS` (`NETWORK`, code `1001`): `error.data[1]` is the status;
|
||||
- `LICENSE_REQUEST_FAILED` (`DRM`, code `6007`): `error.data[0]` is a nested
|
||||
Shaka networking error;
|
||||
- `SERVER_CERTIFICATE_REQUEST_FAILED` (`DRM`, code `6017`):
|
||||
`error.data[0]` is a nested Shaka networking error.
|
||||
|
||||
The boundary reads a status only from an exact `NETWORK + BAD_HTTP_STATUS`
|
||||
shape, directly or through those two exact documented nested-error layouts.
|
||||
It validates the integer protocol range and copies only the number. Status-like
|
||||
values in any other data position or arbitrary object are ignored.
|
||||
|
||||
## Failure And Stage Mapping
|
||||
|
||||
Failure uses exact category/code pairs:
|
||||
|
||||
- allowlisted online network codes in the `NETWORK` category → `network`;
|
||||
- allowlisted DRM codes in the `DRM` category → `drm`;
|
||||
- exact manifest encryption/key-system codes → `drm`;
|
||||
- exact media codes in the `MEDIA` category → `media`;
|
||||
- exact manifest/parsing codes in the `MANIFEST` category → `manifest`;
|
||||
- `DASH_UNSUPPORTED_CONTAINER` and
|
||||
`CONTENT_UNSUPPORTED_BY_BROWSER` → `media`, because the public descriptions
|
||||
prove a media support failure but the latter does not distinguish container
|
||||
from codec;
|
||||
- inconsistent category/code pairs, internal/test-only ambiguity, and
|
||||
`RESTRICTIONS_CANNOT_BE_MET` → `unknown`.
|
||||
|
||||
Stage is narrower than failure:
|
||||
|
||||
- exact manifest codes → `manifest`;
|
||||
- exact segment/index/init parsing codes and `SEGMENT_MISSING` → `segment`;
|
||||
- exact MediaSource/video pipeline codes → `media`;
|
||||
- exact license request/response/server-selection/expiry codes → `license`;
|
||||
- everything else → `unknown`.
|
||||
|
||||
Messages and arbitrary data never affect either value.
|
||||
|
||||
## User-facing Classification
|
||||
|
||||
No new top-level diagnostic code is needed:
|
||||
|
||||
1. `failure=network` → `network-error`.
|
||||
2. `failure=drm` → `drm-or-encryption`.
|
||||
3. Exact `DASH_UNSUPPORTED_CONTAINER` →
|
||||
`unsupported-container`.
|
||||
4. Exact MediaSource operation or video-element failure codes that identify
|
||||
the media pipeline → `media-decode-error`.
|
||||
5. Manifest parsing, generic media parsing/transformation, ambiguous
|
||||
browser-content support, restrictions, unknown values, and inconsistent
|
||||
pairs → `unknown-playback-error`.
|
||||
|
||||
Shaka 5.2.2 has no exact public code that distinguishes an unsupported codec
|
||||
from an unsupported container for the generic
|
||||
`CONTENT_UNSUPPORTED_BY_BROWSER` case. It therefore remains unknown instead of
|
||||
becoming a false codec or container diagnosis. No Shaka path emits
|
||||
`browser-access-error` without a structured public access code.
|
||||
|
||||
## Session And Adapter Flow
|
||||
|
||||
`ShakaVideoSession` owns disposition:
|
||||
|
||||
- module-load rejection, unsupported browser capability, and `load()`
|
||||
rejection are terminal;
|
||||
- a public error event is terminal only for the exact critical severity;
|
||||
- a recoverable event is passed through the shared evidence/classifier gate,
|
||||
returns no diagnostic, and leaves the player attached;
|
||||
- an event with unknown severity is ignored because terminal state is not
|
||||
proven;
|
||||
- a terminal diagnostic tears down the engine once;
|
||||
- ClearKey sources keep the existing rule that external fallback is
|
||||
unavailable because the external player never receives key configuration.
|
||||
|
||||
HTML5 and ArtPlayer keep their current Shaka session adapters. The structured
|
||||
diagnostic travels through their existing `emitPlaybackIssue` callbacks; no
|
||||
player-selection, source-selection, or VOD failover behavior changes.
|
||||
|
||||
Unsupported playlist DRM remains a pre-engine app diagnostic, but its
|
||||
technical detail becomes a fixed safe description instead of echoing the
|
||||
provider-supplied license string.
|
||||
|
||||
## User Interface
|
||||
|
||||
The existing technical “Error details” row renders only
|
||||
`ShakaPlaybackEvidence`, for example:
|
||||
|
||||
`stage=unknown · failure=network · severity=recoverable · category=network · code=1001 · disposition=terminal · HTTP 503`
|
||||
|
||||
When structured Shaka evidence exists, legacy `details` and native message
|
||||
fields are ignored even if a caller accidentally supplies them. Existing
|
||||
titles, descriptions, HTTP badge, Retry, Copy URL, explicit MPV/VLC actions,
|
||||
and layout remain unchanged. No translation or visual styling change is
|
||||
required.
|
||||
|
||||
## Testing
|
||||
|
||||
Use test-driven development:
|
||||
|
||||
- Compare the allowlist/version with the real installed Shaka 5.2.2 runtime.
|
||||
- Exercise real documented direct and nested HTTP error shapes.
|
||||
- Prove all unsafe data fields and misleading messages are excluded.
|
||||
- Cover exact network, DRM, manifest, container, media, restrictions,
|
||||
mismatched, and unknown classification.
|
||||
- Prove recoverable classification returns no terminal diagnostic.
|
||||
- Prove a recoverable-severity load rejection is terminal by lifecycle.
|
||||
- Prove recoverable and unknown-severity events leave the session running.
|
||||
- Prove a critical event during an in-flight load emits once and teardown does
|
||||
not duplicate the later rejection.
|
||||
- Cover ClearKey fallback suppression and ArtPlayer adapter routing.
|
||||
- Prove the rendered detail row uses only sanitized Shaka evidence.
|
||||
|
||||
Run the complete `ui-playback` unit target, its lint target, web typecheck,
|
||||
i18n validation, release-note validation, and the repository test-impact pass.
|
||||
No E2E is required because the player workflow, controls, source routing, and
|
||||
integration lifecycle are unchanged; the event/rejection semantics are
|
||||
covered at the session and adapter boundaries.
|
||||
|
||||
## Documentation And Release Note
|
||||
|
||||
Update `docs/architecture/embedded-inline-playback.md` as the canonical
|
||||
browser diagnostic contract and the Shaka engine section in
|
||||
`docs/architecture/m3u-playlist-module.md`. Keep the Shaka summaries in
|
||||
`CLAUDE.md` and `AGENTS.md` current. Add a `fix(playback)` release note because
|
||||
users receive safer technical details and more accurate Shaka diagnoses.
|
||||
Reference in new issue
Block a user