mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
docs(playback): design structured VHS diagnostics
This commit is contained in:
1 parent
46c7713841
commit
04faa50548
2 files changed
+816
No files matched your search
@@ -0,0 +1,518 @@
|
||||
# Structured Video.js/VHS 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:** Add a safe, exact-value Video.js/VHS evidence contract for the
|
||||
default web player without depending on private VHS internals.
|
||||
|
||||
**Architecture:** Read the public Video.js `MediaError` inside the existing
|
||||
`Player#error` listener, detect active VHS through the documented
|
||||
`player.tech().vhs` runtime property, and sanitize the error into a small
|
||||
allowlisted evidence value. Classify only confirmed public values, keep
|
||||
generic non-VHS Video.js errors on the native path, and render only the
|
||||
sanitized evidence.
|
||||
|
||||
**Tech Stack:** Angular 21, TypeScript 5.9, Video.js 8.23.9, VHS 3.17.5, 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/.pnpm/video.js@8.23.9/node_modules/video.js/dist/types/media-error.d.ts`
|
||||
- Verify:
|
||||
`node_modules/.pnpm/@videojs+http-streaming@3.17.5_video.js@8.23.9/node_modules/@videojs/http-streaming/README.md`
|
||||
- Verify:
|
||||
`node_modules/.pnpm/@videojs+http-streaming@3.17.5_video.js@8.23.9/node_modules/@videojs/http-streaming/src/videojs-http-streaming.js`
|
||||
|
||||
- [x] **Step 1: Install locked dependencies**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
```
|
||||
|
||||
Expected: exit 0 without changing `pnpm-lock.yaml`.
|
||||
|
||||
- [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: 85 suites and 765 tests pass before implementation.
|
||||
|
||||
- [x] **Step 4: Audit exact installed and upstream versions**
|
||||
|
||||
Confirm:
|
||||
|
||||
```text
|
||||
video.js = 8.23.9
|
||||
@videojs/http-streaming = 3.17.5
|
||||
Video.js v8.23.9 tag = 81b3cb429fae8dd00659ac5d3b0b1d2d20a283cb
|
||||
VHS v3.17.5 tag = a9f9d7ac0264b373f14da1bb2f2e7fe8f2775c4f
|
||||
```
|
||||
|
||||
Read the public Video.js `MediaError` and player error API, the VHS README
|
||||
runtime properties/events, and the tagged upstream error/recovery tests.
|
||||
Reject private request/loaders and undocumented retry/exclusion events from
|
||||
the production design.
|
||||
|
||||
### Task 1: Drive The VHS Evidence Boundary From Failing Tests
|
||||
|
||||
**Files:**
|
||||
- Create:
|
||||
`libs/ui/playback/src/lib/playback-diagnostics/vhs-playback-evidence.util.spec.ts`
|
||||
- Create:
|
||||
`libs/ui/playback/src/lib/playback-diagnostics/vhs-playback-evidence.util.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.model.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing allowlist and sanitizer tests**
|
||||
|
||||
In `vhs-playback-evidence.util.spec.ts`, import the actual installed Video.js
|
||||
runtime and the wished-for boundary:
|
||||
|
||||
```typescript
|
||||
import videoJs from 'video.js';
|
||||
import {
|
||||
VhsPlaybackEngineType,
|
||||
createVhsPlaybackEvidence,
|
||||
} from './vhs-playback-evidence.util';
|
||||
|
||||
it('matches the installed public videojs.Error identifiers', () => {
|
||||
expect(Object.values(VhsPlaybackEngineType).sort()).toEqual(
|
||||
Object.values(videoJs.Error).sort()
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
Add installed-runtime-shaped inputs for:
|
||||
|
||||
```typescript
|
||||
const unsafeError = {
|
||||
code: 4,
|
||||
status: 503,
|
||||
message:
|
||||
'HLS playlist request error at URL: ' +
|
||||
'https://provider.example/live.m3u8?token=secret',
|
||||
metadata: {
|
||||
errorType: videoJs.Error.NetworkBadStatus,
|
||||
requestType: 'hls-playlist',
|
||||
uri: 'https://provider.example/live.m3u8?token=secret',
|
||||
headers: { Authorization: 'Bearer secret' },
|
||||
responseText: 'provider body secret',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Expect only:
|
||||
|
||||
```typescript
|
||||
{
|
||||
engineType: 'networkbadstatus',
|
||||
mediaErrorCode: 4,
|
||||
disposition: 'terminal',
|
||||
stage: 'unknown',
|
||||
httpStatus: 503,
|
||||
}
|
||||
```
|
||||
|
||||
Serialize the evidence and prove it contains none of the URL, token, header,
|
||||
body, message, request type, or arbitrary metadata sentinels.
|
||||
|
||||
Cover:
|
||||
|
||||
- every public `videojs.Error` value;
|
||||
- unknown and malformed error types;
|
||||
- standard code boundaries 0/5 and invalid values;
|
||||
- HTTP boundaries 399/400/599/600 and non-integers;
|
||||
- exact HLS playlist, DASH manifest, and segment-operation stage mappings.
|
||||
|
||||
- [ ] **Step 2: Write failing classifier tests**
|
||||
|
||||
In `playback-diagnostics.util.spec.ts`, add wished-for
|
||||
`classifyVhsPlaybackIssue` cases:
|
||||
|
||||
```typescript
|
||||
expect(classifyVhsPlaybackIssue(networkError, metadata)).toEqual(
|
||||
expect.objectContaining({
|
||||
code: PlaybackDiagnosticCode.NetworkError,
|
||||
source: PlaybackDiagnosticSource.Vhs,
|
||||
httpStatus: 503,
|
||||
externalFallbackRecommended: false,
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
Add regressions proving:
|
||||
|
||||
- exact network types classify as `network-error` without message matching;
|
||||
- exact `streamingfailedtodecryptsegment` classifies as
|
||||
`drm-or-encryption`;
|
||||
- standard code 5 classifies as `drm-or-encryption`;
|
||||
- generic VHS code 3 stays `unknown-playback-error`;
|
||||
- unknown provider values and misleading messages stay unknown;
|
||||
- the diagnostic contains no `nativeErrorMessage`;
|
||||
- the top-level status and structured evidence are retained.
|
||||
|
||||
Keep the existing generic native code-3 expectation unchanged.
|
||||
|
||||
- [ ] **Step 3: Run the focused tests to verify RED**
|
||||
|
||||
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/playback-diagnostics/vhs-playback-evidence.util.spec.ts \
|
||||
libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.spec.ts \
|
||||
--runInBand
|
||||
```
|
||||
|
||||
Expected: FAIL because the VHS evidence model, extractor, source, and
|
||||
classifier do not exist.
|
||||
|
||||
- [ ] **Step 4: Add the minimal evidence model**
|
||||
|
||||
In `playback-diagnostics.model.ts`, add const objects and extracted types:
|
||||
|
||||
```typescript
|
||||
export const VhsPlaybackDisposition = {
|
||||
Terminal: 'terminal',
|
||||
} as const;
|
||||
|
||||
export const VhsPlaybackStage = {
|
||||
Manifest: 'manifest',
|
||||
Playlist: 'playlist',
|
||||
Segment: 'segment',
|
||||
Unknown: 'unknown',
|
||||
} as const;
|
||||
|
||||
export const VhsPlaybackMediaErrorCode = {
|
||||
Custom: 0,
|
||||
Aborted: 1,
|
||||
Network: 2,
|
||||
Decode: 3,
|
||||
SourceNotSupported: 4,
|
||||
Encrypted: 5,
|
||||
Unknown: 'unknown',
|
||||
} as const;
|
||||
```
|
||||
|
||||
Define `VhsPlaybackEngineType`, including an `unknown` fallback, and
|
||||
`VhsPlaybackEvidence` with engine type, validated media error code, terminal
|
||||
disposition, stage, and optional status. Add:
|
||||
|
||||
```typescript
|
||||
readonly vhs?: VhsPlaybackEvidence;
|
||||
```
|
||||
|
||||
to `PlaybackDiagnostic`, and add `Vhs: 'vhs'` to
|
||||
`PlaybackDiagnosticSource`.
|
||||
|
||||
- [ ] **Step 5: Implement the allowlisted extractor**
|
||||
|
||||
In `vhs-playback-evidence.util.ts`:
|
||||
|
||||
- define one const object containing the exact installed public
|
||||
`videojs.Error` strings;
|
||||
- validate `metadata.errorType` through a readonly set;
|
||||
- validate only standard error codes 0 through 5;
|
||||
- validate only integer HTTP status 400 through 599;
|
||||
- map only exact public parser/segment identifiers to stages;
|
||||
- return a fresh object containing only the evidence fields;
|
||||
- never read the message or any metadata key other than `errorType`.
|
||||
|
||||
- [ ] **Step 6: Implement exact classification**
|
||||
|
||||
In `playback-diagnostics.util.ts`, add:
|
||||
|
||||
```typescript
|
||||
export function classifyVhsPlaybackIssue(
|
||||
error: NativePlaybackErrorInput,
|
||||
metadata: PlaybackSourceMetadata
|
||||
): PlaybackDiagnostic
|
||||
```
|
||||
|
||||
Create evidence once, then classify by validated HTTP status, exact network
|
||||
types, standard network code, exact decrypt type, standard encrypted code, or
|
||||
known unsupported container. Keep all remaining evidence unknown. Store the
|
||||
evidence on the diagnostic, copy its status into the existing `httpStatus`,
|
||||
copy its validated code into `nativeErrorCode`, and do not copy the error
|
||||
message or arbitrary metadata.
|
||||
|
||||
- [ ] **Step 7: Run focused tests to verify GREEN**
|
||||
|
||||
Run the command from step 3.
|
||||
|
||||
Expected: PASS for allowlisting, privacy, exact classification, code-3
|
||||
unknown behavior, and unchanged native tests.
|
||||
|
||||
### Task 2: Route Only Active VHS Errors Through The Boundary
|
||||
|
||||
**Files:**
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/vjs-player/vjs-player.types.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/vjs-player/vjs-player.types.spec.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/vjs-player/vjs-player.component.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Write failing active-VHS detection tests**
|
||||
|
||||
In `vjs-player.types.spec.ts`, add expectations for a wished-for
|
||||
`hasActiveVhsSourceHandler` helper:
|
||||
|
||||
```typescript
|
||||
expect(hasActiveVhsSourceHandler(playerWithVhs)).toBe(true);
|
||||
expect(hasActiveVhsSourceHandler(playerWithoutVhs)).toBe(false);
|
||||
expect(hasActiveVhsSourceHandler(throwingPlayer)).toBe(false);
|
||||
```
|
||||
|
||||
The helper may inspect only the documented `player.tech().vhs` property.
|
||||
|
||||
- [ ] **Step 2: Write failing component routing regressions**
|
||||
|
||||
Extend the player harness with an optional `vhs` object on the current tech.
|
||||
Add tests proving:
|
||||
|
||||
- active VHS + real network error shape emits a structured VHS network
|
||||
diagnostic;
|
||||
- the unsafe VHS message and metadata are absent;
|
||||
- active VHS + generic code 3 emits unknown;
|
||||
- no VHS + native code 3 retains `media-decode-error`;
|
||||
- a populated `player.error()` still wins over `video.error`.
|
||||
|
||||
- [ ] **Step 3: Run the focused component tests to verify RED**
|
||||
|
||||
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/vjs-player/vjs-player.types.spec.ts \
|
||||
libs/ui/playback/src/lib/vjs-player/vjs-player.component.spec.ts \
|
||||
--runInBand
|
||||
```
|
||||
|
||||
Expected: FAIL because active VHS detection and routing do not exist.
|
||||
|
||||
- [ ] **Step 4: Implement the public runtime check and routing**
|
||||
|
||||
Add a guarded helper in `vjs-player.types.ts` that returns true only when
|
||||
`player.tech()?.vhs` is a non-null object. In
|
||||
`VjsPlayerComponent.handleVideoJsError`, call
|
||||
`classifyVhsPlaybackIssue` only when that helper is true and
|
||||
`player.error()` returned an error. Otherwise keep
|
||||
`classifyNativePlaybackIssue(playerError ?? video.error, metadata)`.
|
||||
|
||||
Do not add xhr hooks, VHS loader access, retry listeners, or private fields.
|
||||
|
||||
- [ ] **Step 5: Run the focused component tests to verify GREEN**
|
||||
|
||||
Run the command from step 3.
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 3: Render Only Structured VHS Details
|
||||
|
||||
**Files:**
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/web-player-view/web-player-view-diagnostics.utils.ts`
|
||||
- Modify:
|
||||
`libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing safe rendering regression**
|
||||
|
||||
Create a VHS diagnostic carrying sanitized evidence plus provider-secret
|
||||
sentinels in fields that must not be rendered. Expect:
|
||||
|
||||
```typescript
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_ERROR_DETAILS',
|
||||
value:
|
||||
'stage=unknown · type=networkbadstatus · code=4 · ' +
|
||||
'disposition=terminal · HTTP 503',
|
||||
}
|
||||
```
|
||||
|
||||
Prove the rendered details do not contain the provider URL, token, headers,
|
||||
message, response body, or arbitrary metadata.
|
||||
|
||||
- [ ] **Step 2: Run the focused view test to verify RED**
|
||||
|
||||
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/web-player-view/web-player-view.component.spec.ts \
|
||||
--runInBand
|
||||
```
|
||||
|
||||
Expected: FAIL because the formatter has no VHS evidence branch.
|
||||
|
||||
- [ ] **Step 3: Implement deterministic VHS formatting**
|
||||
|
||||
In `formatDiagnosticErrorDetails`, add a VHS branch before generic native
|
||||
formatting. Build the summary only from evidence stage, engine type, media
|
||||
error code, disposition, and optional status. Add `vhs` to the diagnostic
|
||||
source formatter as `Video.js / VHS`.
|
||||
|
||||
- [ ] **Step 4: Run the focused view test to verify GREEN**
|
||||
|
||||
Run the command from step 2.
|
||||
|
||||
Expected: PASS and the existing HLS summary remains unchanged.
|
||||
|
||||
### Task 4: Document, Release-Note, And Validate
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/architecture/embedded-inline-playback.md`
|
||||
- Create: `.changes/playback-structured-videojs-diagnostics.md`
|
||||
- Verify: `AGENTS.md`
|
||||
- Verify: `CLAUDE.md`
|
||||
|
||||
- [ ] **Step 1: Update the canonical diagnostic contract**
|
||||
|
||||
Document:
|
||||
|
||||
- Video.js 8.23.9 / VHS 3.17.5 public evidence boundary;
|
||||
- exact allowlist and conservative classification;
|
||||
- safe stage mapping;
|
||||
- terminal `Player#error` semantics and recoverable VHS suppression;
|
||||
- rejected private request/loader fields and unsafe payloads;
|
||||
- active-VHS code 3 remaining unknown.
|
||||
|
||||
No `AGENTS.md` or `CLAUDE.md` change is expected because neither currently
|
||||
describes this diagnostic boundary. Re-check both after the runtime diff.
|
||||
|
||||
- [ ] **Step 2: Add the release note**
|
||||
|
||||
Create:
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: fix
|
||||
area: playback
|
||||
---
|
||||
|
||||
The default web player now reports safer, more accurate streaming errors:
|
||||
confirmed network and encrypted-segment failures keep structured details,
|
||||
while ambiguous Video.js errors remain unknown instead of suggesting the
|
||||
wrong cause.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run targeted and affected validation**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
pnpm nx test ui-playback
|
||||
pnpm nx lint ui-playback
|
||||
pnpm nx typecheck web
|
||||
pnpm run i18n:validate
|
||||
pnpm run release:notes:validate
|
||||
```
|
||||
|
||||
If `web:typecheck` is not the actual target name, inspect
|
||||
`pnpm nx show project web` and run the repository's declared typecheck target.
|
||||
|
||||
Expected: all commands exit 0.
|
||||
|
||||
- [ ] **Step 4: Complete the test-impact pass**
|
||||
|
||||
Use:
|
||||
|
||||
```bash
|
||||
pnpm nx show projects --withTarget test
|
||||
pnpm nx show projects --withTarget e2e
|
||||
```
|
||||
|
||||
Record that `ui-playback` unit/component tests, lint, web typecheck, i18n, and
|
||||
release-note validation cover the changed boundary. E2E is skipped unless the
|
||||
implementation changes workflow, routing, playback lifecycle, or player
|
||||
recovery behavior.
|
||||
|
||||
### Task 5: Independent Review, Final Verification, And Ready PR
|
||||
|
||||
**Files:**
|
||||
- Review: complete diff from `origin/master...HEAD`
|
||||
|
||||
- [ ] **Step 1: Run an independent local Codex review**
|
||||
|
||||
Provide the reviewer with the user constraints, exact installed versions, and
|
||||
the full diff. Ask only for actionable P0/P1/P2 correctness, privacy, public
|
||||
API stability, event-ordering, regression, test, and scope findings.
|
||||
|
||||
- [ ] **Step 2: Fix every valid P0/P1/P2 finding with TDD**
|
||||
|
||||
For each finding, add or adjust a failing regression test first, verify RED,
|
||||
apply the minimal fix, and verify GREEN. Reject incorrect findings with
|
||||
specific source/test evidence.
|
||||
|
||||
- [ ] **Step 3: Repeat the independent review**
|
||||
|
||||
Run the same full-diff review again. Expected: no actionable P0/P1/P2
|
||||
findings.
|
||||
|
||||
- [ ] **Step 4: Run fresh full validation**
|
||||
|
||||
Repeat every command from Task 4 step 3 after the final review fix. Inspect
|
||||
the complete output and confirm zero failures.
|
||||
|
||||
- [ ] **Step 5: Inspect final scope and documentation**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git diff --check origin/master...HEAD
|
||||
git diff --stat origin/master...HEAD
|
||||
git diff origin/master...HEAD
|
||||
```
|
||||
|
||||
Confirm:
|
||||
|
||||
- no hls.js contract changes unless a real regression required one;
|
||||
- no Shaka/mpegts redesign;
|
||||
- no private VHS production access;
|
||||
- no unsafe error payload retention/rendering;
|
||||
- docs and one release note are present;
|
||||
- no unrelated files changed.
|
||||
|
||||
- [ ] **Step 6: Commit, push, and create the ready PR**
|
||||
|
||||
Use a conventional `fix(playback): ...` commit for runtime behavior. Push
|
||||
`agent/structured-vhs-diagnostics` and create a non-draft PR with the evidence
|
||||
summary, privacy boundary, tests, validation, E2E rationale, and review result.
|
||||
@@ -0,0 +1,298 @@
|
||||
# Structured Video.js/VHS Diagnostics
|
||||
|
||||
## Context
|
||||
|
||||
PR #1314 made generic native playback diagnostics preserve Video.js HTTP
|
||||
status and `metadata.errorType`, and stopped treating an ambiguous
|
||||
`MediaError` code 4 as codec evidence. PR #1316 added a separate structured
|
||||
hls.js boundary for the HTML5 and ArtPlayer engines. Video.js remains the
|
||||
default web player, but its HLS/DASH implementation is VHS rather than hls.js,
|
||||
so the hls.js evidence contract does not apply to it.
|
||||
|
||||
The locked workspace installs:
|
||||
|
||||
- Video.js `8.23.9`;
|
||||
- bundled `@videojs/http-streaming` (VHS) `3.17.5`;
|
||||
- an unrelated Video.js `7.21.7` / VHS `2.16.3` pair nested under the legacy
|
||||
aspect-ratio plugin.
|
||||
|
||||
The application imports the root Video.js `8.23.9` build. Its package declares
|
||||
VHS `^3.17.5`, and pnpm resolves that dependency to `3.17.5`.
|
||||
|
||||
The audit compared the installed sources with the exact upstream tags:
|
||||
|
||||
- Video.js `v8.23.9`, commit
|
||||
`81b3cb429fae8dd00659ac5d3b0b1d2d20a283cb`;
|
||||
- VHS `v3.17.5`, commit
|
||||
`a9f9d7ac0264b373f14da1bb2f2e7fe8f2775c4f`.
|
||||
|
||||
The installed VHS `error-codes.js`, `videojs-http-streaming.js`, and
|
||||
`playlist-controller.js` match the tagged sources.
|
||||
|
||||
## Goals
|
||||
|
||||
- Add a minimal allowlisted evidence boundary for terminal Video.js/VHS
|
||||
errors.
|
||||
- Retain only public Video.js `MediaError` fields and exact public
|
||||
`videojs.Error` identifiers.
|
||||
- Classify only exact confirmed values; unknown values remain unknown.
|
||||
- Keep recoverable VHS playlist/segment handling from becoming a terminal
|
||||
IPTVnator diagnostic.
|
||||
- Avoid retaining or rendering VHS/provider URLs, headers, xhr objects,
|
||||
response bodies, messages, credentials, or arbitrary metadata.
|
||||
- Correct the misleading case where VHS promotes a generic internal object or
|
||||
string to code 3 even though no media-decode cause was established.
|
||||
- Keep generic native Video.js playback behavior unchanged when VHS is not the
|
||||
active source handler.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing the hls.js evidence contract from PR #1316.
|
||||
- A Shaka or mpegts.js diagnostic redesign.
|
||||
- Inferring CORS, mixed content, CSP, private-network access, codec, DRM, or a
|
||||
request stage from messages or indirect signals.
|
||||
- Reading VHS playlist loaders, segment loaders, request objects, or other
|
||||
private implementation state.
|
||||
- Listening to undocumented VHS retry/exclusion events in production.
|
||||
- Diagnostic history, persistence, correlation, stream probes, automatic
|
||||
failover, or a cross-player recommendation matrix.
|
||||
|
||||
## Approaches Considered
|
||||
|
||||
### Public Video.js error boundary (selected)
|
||||
|
||||
Read `player.error()` inside the public `Player#error` listener. When the
|
||||
documented `player.tech().vhs` runtime property is present, sanitize the error
|
||||
into a small `VhsPlaybackEvidence` object. Validate `metadata.errorType`
|
||||
against the exact values exported by `videojs.Error`, validate the standard
|
||||
`MediaError` code and HTTP status, and derive a stage only where the public
|
||||
engine identifier itself names that stage.
|
||||
|
||||
This approach adds structured evidence without depending on VHS loaders or
|
||||
request objects. It also lets generic non-VHS Video.js errors continue through
|
||||
the existing native classifier.
|
||||
|
||||
### Observe VHS xhr hooks
|
||||
|
||||
VHS documents `vhs.xhr.onRequest` and `onResponse`, but request hooks would
|
||||
require correlating mutable request objects with a later terminal error. They
|
||||
also expose URLs, headers, response objects, and provider payloads at exactly
|
||||
the boundary that must stay sanitized. Request completion is not terminal
|
||||
playback disposition: VHS may retry, exclude a rendition, or recover. This
|
||||
approach is rejected.
|
||||
|
||||
### Read VHS loaders and retry events
|
||||
|
||||
The installed implementation carries precise `requestType`, xhr, playlist,
|
||||
segment, and key context internally. Those objects and their event ordering
|
||||
are not part of the documented stable error API. Depending on
|
||||
`playlistController_`, loader error objects, `retryplaylist`, or
|
||||
`excludeplaylist` would violate the scope constraint and make updates to VHS
|
||||
risky. This approach is rejected.
|
||||
|
||||
### Stop after the audit
|
||||
|
||||
Stopping would be correct if the public API exposed nothing beyond PR #1314.
|
||||
The audit found a safe increment: `videojs.Error` is a public exact-value
|
||||
allowlist, `player.error()` is populated before `Player#error`, and active VHS
|
||||
can be detected through a documented runtime property. That is enough to
|
||||
structure evidence, suppress unsafe message/metadata retention, and avoid a
|
||||
false generic code-3 decode diagnosis.
|
||||
|
||||
## Stable Public Evidence
|
||||
|
||||
Video.js `8.23.9` documents and types these public fields:
|
||||
|
||||
- `player.error()` returns the current Video.js `MediaError`;
|
||||
- `MediaError.code` carries standard codes 0 through 5;
|
||||
- `MediaError.status` is an optional plugin-supplied status;
|
||||
- `MediaError.metadata.errorType` is expected to align with
|
||||
`videojs.Error`;
|
||||
- `Player#error` is emitted after `player.error_` has been replaced with the
|
||||
new `MediaError`;
|
||||
- `player.tech().vhs` is a documented VHS runtime property while VHS is in
|
||||
use.
|
||||
|
||||
Video.js `8.23.9` publicly exports these exact `videojs.Error` values:
|
||||
|
||||
- `networkbadstatus`;
|
||||
- `networkrequestfailed`;
|
||||
- `networkrequestaborted`;
|
||||
- `networkrequesttimeout`;
|
||||
- `networkbodyparserfailed`;
|
||||
- `streaminghlsplaylistparsererror`;
|
||||
- `streamingdashmanifestparsererror`;
|
||||
- `streamingcontentsteeringparsererror`;
|
||||
- `streamingvttparsererror`;
|
||||
- `streamingfailedtoselectnextsegment`;
|
||||
- `streamingfailedtodecryptsegment`;
|
||||
- `streamingfailedtotransmuxsegment`;
|
||||
- `streamingfailedtoappendsegment`;
|
||||
- `streamingcodecschangeerror`.
|
||||
|
||||
The allowlist is intentionally version-locked. A regression test compares it
|
||||
with the installed `videojs.Error` export so a dependency update requires a
|
||||
new audit instead of silently accepting new engine values.
|
||||
|
||||
## Rejected Evidence
|
||||
|
||||
VHS `3.17.5` internally adds fields such as `requestType`, `uri`, `headers`,
|
||||
`error`, xhr objects, response text, playlist objects, and segment context.
|
||||
Those values can contain credentials or provider data and are not required by
|
||||
the public Video.js `ErrorMetadata` contract. The boundary must not read or
|
||||
copy them.
|
||||
|
||||
The following are also rejected:
|
||||
|
||||
- error `message`, because VHS messages embed request URLs;
|
||||
- `responseText`, response data, and response bodies;
|
||||
- request/response headers;
|
||||
- arbitrary metadata keys or provider objects;
|
||||
- `requestType`, because its propagation through `player.error()` is an
|
||||
implementation detail rather than a documented stable contract;
|
||||
- status zero as CORS or browser-access evidence;
|
||||
- message fragments as codec, DRM, network, access, or stage evidence.
|
||||
|
||||
The existing `PlaybackDiagnostic.sourceUrl` remains available to the
|
||||
pre-existing Retry, Copy URL, and explicit external-player workflows. No URL
|
||||
from the Video.js/VHS error object is copied into evidence or technical
|
||||
details.
|
||||
|
||||
## Evidence Contract
|
||||
|
||||
`VhsPlaybackEvidence` contains:
|
||||
|
||||
- `engineType`: one exact installed `videojs.Error` value, otherwise
|
||||
`unknown`;
|
||||
- `mediaErrorCode`: a validated standard `MediaError` code 0 through 5,
|
||||
otherwise `unknown`;
|
||||
- `disposition`: `terminal`;
|
||||
- `stage`: `manifest`, `playlist`, `segment`, or `unknown`;
|
||||
- optional `httpStatus`: an integer from 400 through 599 copied only from the
|
||||
public top-level `MediaError.status`.
|
||||
|
||||
There is no recoverable evidence object. IPTVnator creates this boundary only
|
||||
from the public `Player#error` event after Video.js has stored the final error.
|
||||
Recoverable VHS handling remains inside VHS and produces no terminal
|
||||
diagnostic.
|
||||
|
||||
## Stage Mapping
|
||||
|
||||
Stage is derived only from exact public engine identifiers:
|
||||
|
||||
- `streaminghlsplaylistparsererror` → `playlist`;
|
||||
- `streamingdashmanifestparsererror` → `manifest`;
|
||||
- `streamingfailedtoselectnextsegment` → `segment`;
|
||||
- `streamingfailedtodecryptsegment` → `segment`;
|
||||
- `streamingfailedtotransmuxsegment` → `segment`;
|
||||
- `streamingfailedtoappendsegment` → `segment`;
|
||||
- all network identifiers, content-steering/VTT parser errors, codec-change
|
||||
errors, and unrecognized values → `unknown`.
|
||||
|
||||
Network errors remain stage-unknown even if internal metadata happens to carry
|
||||
`requestType: hls-playlist`, `hls-segment`, or `hls-key`. The public error
|
||||
contract does not guarantee those values.
|
||||
|
||||
## Classification
|
||||
|
||||
`classifyVhsPlaybackIssue` uses this precedence:
|
||||
|
||||
1. A validated HTTP 4xx/5xx status produces `network-error`.
|
||||
2. Exact public network error types produce `network-error`.
|
||||
3. Standard `MediaError` code 2 produces `network-error`.
|
||||
4. Exact `streamingfailedtodecryptsegment` produces
|
||||
`drm-or-encryption`.
|
||||
5. Standard `MediaError` code 5 produces `drm-or-encryption`.
|
||||
6. A known browser-incompatible source container may still produce
|
||||
`unsupported-container`.
|
||||
7. Everything else produces `unknown-playback-error`.
|
||||
|
||||
Generic VHS code 3 does not produce `media-decode-error`. VHS assigns code 3
|
||||
to object errors without a code and to string errors before calling
|
||||
`player.error()`, including the terminal “no available playlists” path.
|
||||
Therefore code 3 is not sufficient decode evidence on the VHS path.
|
||||
|
||||
The boundary does not classify:
|
||||
|
||||
- codec incompatibility from codec-change, transmux, append, or message text;
|
||||
- DRM from key-request/load failure;
|
||||
- browser access from status zero or generic request failure;
|
||||
- media decode from code 3 alone.
|
||||
|
||||
Generic Video.js playback without active VHS continues to use the existing
|
||||
native classifier, so a native code 3 remains a media-decode diagnostic.
|
||||
|
||||
## Runtime And Event Ordering
|
||||
|
||||
VHS `3.17.5` handles playlist and segment failures before the public terminal
|
||||
error:
|
||||
|
||||
- a failed rendition can be excluded and another selected;
|
||||
- a single finite-exclusion rendition is retried;
|
||||
- previously excluded renditions can be re-included;
|
||||
- segment timeouts can trigger ABR recovery;
|
||||
- aborted segment requests are ignored as non-errors;
|
||||
- only an unrecoverable playlist-controller error reaches
|
||||
`player.error(...)`.
|
||||
|
||||
Video.js `8.23.9` constructs and stores the `MediaError`, then synchronously
|
||||
fires `Player#error`. The component therefore reads the final public error
|
||||
inside its existing listener and marks it terminal. IPTVnator does not
|
||||
subscribe to internal VHS recovery events.
|
||||
|
||||
## Component Flow
|
||||
|
||||
`VjsPlayerComponent` keeps one `Player#error` listener:
|
||||
|
||||
1. Read `player.error()` before falling back to the native video error.
|
||||
2. Detect active VHS through the documented `player.tech().vhs` runtime
|
||||
property.
|
||||
3. With active VHS and a Video.js error, call
|
||||
`classifyVhsPlaybackIssue`.
|
||||
4. Otherwise keep the existing `classifyNativePlaybackIssue` path.
|
||||
5. Emit exactly one terminal diagnostic.
|
||||
|
||||
The component does not inspect `playlistController_`, loaders, xhr hooks,
|
||||
request types, retry events, or error messages.
|
||||
|
||||
## User Interface
|
||||
|
||||
Add one deterministic Video.js/VHS technical-detail summary:
|
||||
|
||||
`stage=unknown · type=networkbadstatus · code=4 · disposition=terminal · HTTP 503`
|
||||
|
||||
The summary is built only from `VhsPlaybackEvidence`. It never includes the
|
||||
Video.js message or arbitrary metadata. Existing diagnostic headings,
|
||||
descriptions, HTTP badge, Retry, Copy URL, and explicit MPV/VLC actions remain
|
||||
unchanged; no new translation keys or layout changes are needed.
|
||||
|
||||
## Testing
|
||||
|
||||
Use test-driven development:
|
||||
|
||||
- Compare the allowlist with the actual installed `videojs.Error` export.
|
||||
- Exercise real Video.js public event ordering by setting an error on a real
|
||||
Video.js player and reading `player.error()` from its `error` listener.
|
||||
- Use installed-runtime-shaped VHS errors for 5xx, request failure, timeout,
|
||||
playlist parsing, segment decrypt, generic code 3, and unknown provider
|
||||
metadata.
|
||||
- Prove URLs, headers, response data, messages, credentials, request types,
|
||||
and arbitrary metadata do not survive the boundary or rendered details.
|
||||
- Prove only exact public network/decrypt values affect classification.
|
||||
- Prove a generic active-VHS code 3 stays unknown while non-VHS native code 3
|
||||
remains a media-decode diagnostic.
|
||||
- Prove the component routes active VHS errors through the structured boundary
|
||||
and leaves generic Video.js errors on the native path.
|
||||
- Prove technical details render only the sanitized VHS evidence.
|
||||
|
||||
Run the complete `ui-playback` unit target, its lint target, the web typecheck,
|
||||
i18n validation, release-note validation, and the repository test-impact pass.
|
||||
No E2E is required because the playback workflow, controls, routing, and
|
||||
integration lifecycle are unchanged; the change is confined to the existing
|
||||
terminal error boundary and technical details.
|
||||
|
||||
## Documentation And Release Note
|
||||
|
||||
Update `docs/architecture/embedded-inline-playback.md`, the canonical browser
|
||||
playback diagnostic contract. Add a `fix(playback)` release note because users
|
||||
receive more accurate default-player diagnoses and safer technical details.
|
||||
Reference in new issue
Block a user