diff --git a/docs/architecture/embedded-inline-playback.md b/docs/architecture/embedded-inline-playback.md index 6dbb15a33..0348653e0 100644 --- a/docs/architecture/embedded-inline-playback.md +++ b/docs/architecture/embedded-inline-playback.md @@ -264,6 +264,13 @@ The diagnostic surface covers the inline player viewport when playback fails, wi URL extension metadata is filtered before diagnostics and player selection use it. Web script extensions such as `.php` are not shown as stream containers; explicit media query metadata such as `extension=ts` or `format=m3u8` is preferred when present. +MKV sources are attempted through Chromium's native Matroska path. Video.js +receives `video/matroska` for `.mkv` URLs and explicit query metadata such as +`extension=mkv` or `container=mkv`; ArtPlayer and HTML5 continue to use their +native video paths. This is container support rather than a universal codec +guarantee: native source or decode failures still produce the existing +diagnostic and explicit MPV/VLC fallback. + Portal VOD and episode payloads with `contentInfo` are treated as non-live by the inline players unless `isLive` is explicitly set. If Chromium leaves the underlying MediaSource duration at `Infinity` for a finite TS VOD, the Video.js wrapper normalizes its UI duration from the finite `seekable` or `buffered` range. Embedded MPV uses the same live decision rule and shows an unknown duration placeholder for VOD/episode snapshots until MPV reports a finite duration. This removes the misleading `LIVE` control state without changing stream decoding, diagnostics, or external fallback behavior. When a diagnostic is actionable in Electron, the diagnostic surface may offer `Open in MPV`, `Open in VLC`, `Copy URL`, technical details, and `Retry`. Web builds only expose copy/help text and retry. MPV/VLC fallback requests carry the original `ResolvedPortalPlayback` payload so headers, referer, origin, user-agent, content metadata, and resume offset stay intact. Retry clears the current diagnostic and rebuilds the active inline player inputs; it does not change the saved player setting. diff --git a/docs/superpowers/plans/2026-07-19-mkv-inline-playback.md b/docs/superpowers/plans/2026-07-19-mkv-inline-playback.md new file mode 100644 index 000000000..25c60b643 --- /dev/null +++ b/docs/superpowers/plans/2026-07-19-mkv-inline-playback.md @@ -0,0 +1,342 @@ +# Native Matroska Inline Playback 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:** Let the Video.js inline player attempt MKV playback through Chromium's native Matroska pipeline while preserving codec-dependent diagnostics and external-player fallback. + +**Architecture:** Keep `WebPlayerViewComponent` as the single Video.js source-type router and keep `getPlaybackMediaExtensionFromUrl()` as the canonical parser for path and query metadata. Add only an explicit MKV MIME branch; ArtPlayer and HTML5 remain on their existing native-video paths, and native playback errors remain authoritative. + +**Tech Stack:** Angular 21, TypeScript, Jest/Nx, Electron 41/Chromium 146, agent-browser CDP + +--- + +## Task 1: Add failing Video.js MIME regression tests + +**Files:** + +- Modify: `libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts` +- Test: `libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts` + +- [x] **Step 1: Add a path-extension regression test** + +Add this test beside the existing query-declared HLS test: + +```ts +it('uses the Matroska mime type for MKV paths', () => { + const streamUrl = 'https://example.com/archive/movie.mkv'; + + component.setVjsOptions(streamUrl); + + expect(component.vjsOptions.sources).toEqual([ + { + src: streamUrl, + type: 'video/matroska', + }, + ]); +}); +``` + +- [x] **Step 2: Add a query-metadata regression test** + +```ts +it('uses the Matroska mime type for query-declared MKV streams', () => { + const streamUrl = + 'https://example.com/play?container=mkv&token=signed'; + + component.setVjsOptions(streamUrl); + + expect(component.vjsOptions.sources).toEqual([ + { + src: streamUrl, + type: 'video/matroska', + }, + ]); +}); +``` + +- [x] **Step 3: Run the focused spec and verify RED** + +Run: + +```bash +pnpm nx test ui-playback --skip-nx-cache --runInBand --runTestsByPath libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts +``` + +Expected: the two new expectations fail because the actual source type is still `video/mp4`; existing tests continue to pass. + +## Task 2: Route MKV sources as Matroska + +**Files:** + +- Modify: `libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts` +- Test: `libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts` + +- [x] **Step 1: Add the smallest production change** + +Change the MIME decision to: + +```ts +const mimeType = + extension === 'm3u' || extension === 'm3u8' + ? 'application/x-mpegURL' + : extension === 'ts' || !extension + ? 'video/mp2t' + : extension === 'mkv' + ? 'video/matroska' + : 'video/mp4'; +``` + +Do not add `canPlayType()` preflight or a codec list: the actual native load result remains authoritative. + +- [x] **Step 2: Run the focused spec and verify GREEN** + +Run: + +```bash +pnpm nx test ui-playback --skip-nx-cache --runInBand --runTestsByPath libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts +``` + +Expected: all tests in the spec pass. + +- [x] **Step 3: Run the complete affected unit-test target** + +Run: + +```bash +pnpm nx test ui-playback --skip-nx-cache --runInBand +``` + +Expected: PASS. + +## Task 3: Document the native MKV contract + +**Files:** + +- Modify: `docs/architecture/embedded-inline-playback.md` + +- [x] **Step 1: Extend the codec/container diagnostics section** + +After the URL extension metadata paragraph, document: + +```md +MKV sources are attempted through Chromium's native Matroska path. Video.js +receives `video/matroska` for `.mkv` URLs and explicit query metadata such as +`extension=mkv` or `container=mkv`; ArtPlayer and HTML5 continue to use their +native video paths. This is container support rather than a universal codec +guarantee: native source/decode failures still produce the existing diagnostic +and explicit MPV/VLC fallback. +``` + +- [x] **Step 2: Verify Markdown and whitespace** + +Run: + +```bash +git diff --check +git diff -- docs/architecture/embedded-inline-playback.md +``` + +Expected: no whitespace errors; the diff describes only the new MKV contract. + +## Task 4: Verify the Electron runtime with a supported MKV + +**Files:** + +- Temporary fixture only: `/tmp/iptvnator-mkv-inline-playback/video.mkv` +- No committed binary fixture + +- [x] **Step 1: Download a small H.264/AAC Matroska fixture** + +Run: + +```bash +mkdir -p /tmp/iptvnator-mkv-inline-playback +curl --fail --location https://remotion.media/video.mkv --output /tmp/iptvnator-mkv-inline-playback/video.mkv +``` + +Record the downloaded SHA-256 in the verification notes: + +```bash +shasum -a 256 /tmp/iptvnator-mkv-inline-playback/video.mkv +``` + +- [x] **Step 2: Start an isolated IPTVnator Electron runtime** + +If the standard IPTVnator development ports are free, run in a long-lived +terminal: + +```bash +IPTVNATOR_TRACE_RENDERER_CONSOLE=1 pnpm nx serve electron-backend +``` + +Wait for the IPTVnator renderer target on `127.0.0.1:9222`. If another +worktree already owns the development ports, do not stop it; instead launch +the repository's Electron 41 binary with a temporary BrowserWindow harness on +an unused CDP port and remove the harness after the smoke. + +- [x] **Step 3: Exercise Chromium's native Matroska pipeline in an Electron renderer** + +Use the repository's documented Electron CDP workflow and `agent-browser` to +attach to the IPTVnator page. In the renderer: + +1. Read `/tmp/iptvnator-mkv-inline-playback/video.mkv` through a temporary + localhost range server that returns `Content-Type: video/matroska`. +2. Create a temporary `