feat(playback): add Ferrite (WASM/WebCodecs) inline player for software HEVC#1098
feat(playback): add Ferrite (WASM/WebCodecs) inline player for software HEVC#1098multiduplikator wants to merge 3 commits into
Conversation
…re HEVC Adds Ferrite as a new inline web player — canvas + WASM software decode, with an automatic WebCodecs hardware tier — for software HEVC where the browser has no hardware decoder (web/PWA; requires a cross-origin-isolated context). - ferrite-player component + session/event wiring (live + VOD), IPTVnator-owned Material controls (play/mute/volume/fullscreen, VOD seek bar, software-tier deinterlace select) and a long-press diagnostic overlay; teardown via the facade's canonical destroy() (releases the transferred canvas + clears all listeners). - web-player-view routing (HLS m3u/m3u8 falls back to the default inline player), settings + command-palette registration, i18n, docs, and the engine/worker asset bundling with an opt-in COOP/COEP isolated dev-serve configuration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s control ferrite.js 1.3.2 brings an audio-path overhaul (clean 5.1→stereo downmix + device-rate resample, loudness leveling) and the new "Dyna" dynamics control, all-codec anamorphic SAR, and correct HEVC sizing on browsers with bogus VideoFrame dims (Edge). The audio overhaul, SAR, and HEVC sizing are internal to ferrite.js and apply automatically via the re-bundled 1.3.2 engine assets. - bump ferrite.js ^1.3.0 → ^1.3.2 (lockfile + bundled engine/worker assets). - add a "Dyna" select (Line / RF / Night) to the Ferrite controls, driving the facade's setDrc(0|1|2). Always shown (audio is always decoded on both tiers); resets to Line per stream like the deint mode. Night is a built-in compressor that tames peaks on any codec. - i18n keys (FERRITE_PLAYER.DYNA*) across all locales; component + control specs extended. Deliberately left out (no public ferrite.js API / not appropriate as app settings): the fast non-conformant decode toggle and a persisted default-deinterlace setting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryAdds Ferrite — a
Confidence Score: 4/5Safe to merge; the Ferrite player lifecycle, teardown, and HLS fallback are all correctly implemented with no data loss or crash paths. The implementation is well-structured: teardown always goes through
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant WPV as WebPlayerViewComponent
participant FPC as FerritePlayerComponent
participant FS as ferrite-session
participant FEW as ferrite-event-wiring
participant FP as FerritePlayer (facade)
participant OI as overlay-interactions
WPV->>WPV: "selectedPlayer() == 'ferrite'?"
Note over WPV: HLS fallback: m3u/m3u8 → VideoJs
WPV->>FPC: "@defer renders app-ferrite-player"
FPC->>FPC: effect(): channel + canvasRef ready
FPC->>FS: startFerriteSession(opts)
FS->>FP: "Ferrite.createPlayer({mpegts, isLive, url})"
FS->>FEW: wireFerriteEvents(player, url, sinks)
FEW->>FP: player.on(ERROR, MEDIA_INFO, TIME_UPDATE, ...)
FS->>FP: player.attachCanvas(canvas)
FS->>FP: player.load() + player.play()
FS-->>FPC: returns FerritePlayer
FPC->>OI: wireOverlayInteractions(shell, canvas, cb)
Note over OI: idle-hide timer + long-press debug toggle
FP->>FEW: MEDIA_INFO event
FEW->>FPC: sinks.tier.set / sinks.format.set (outside zone)
FP->>FEW: TIME_UPDATE (throttled 4Hz)
FEW->>FPC: zone.run → emitTimeUpdate
FP->>FEW: ERROR (fatal)
FEW->>WPV: zone.run → emitIssue → handlePlaybackIssue
Note over FPC: channel zap / ngOnDestroy
FPC->>FS: stopFerriteSession(player)
FS->>FP: player.destroy() (outside zone)
Note over FP: releases OffscreenCanvas, terminates workers, clears all listeners
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant WPV as WebPlayerViewComponent
participant FPC as FerritePlayerComponent
participant FS as ferrite-session
participant FEW as ferrite-event-wiring
participant FP as FerritePlayer (facade)
participant OI as overlay-interactions
WPV->>WPV: "selectedPlayer() == 'ferrite'?"
Note over WPV: HLS fallback: m3u/m3u8 → VideoJs
WPV->>FPC: "@defer renders app-ferrite-player"
FPC->>FPC: effect(): channel + canvasRef ready
FPC->>FS: startFerriteSession(opts)
FS->>FP: "Ferrite.createPlayer({mpegts, isLive, url})"
FS->>FEW: wireFerriteEvents(player, url, sinks)
FEW->>FP: player.on(ERROR, MEDIA_INFO, TIME_UPDATE, ...)
FS->>FP: player.attachCanvas(canvas)
FS->>FP: player.load() + player.play()
FS-->>FPC: returns FerritePlayer
FPC->>OI: wireOverlayInteractions(shell, canvas, cb)
Note over OI: idle-hide timer + long-press debug toggle
FP->>FEW: MEDIA_INFO event
FEW->>FPC: sinks.tier.set / sinks.format.set (outside zone)
FP->>FEW: TIME_UPDATE (throttled 4Hz)
FEW->>FPC: zone.run → emitTimeUpdate
FP->>FEW: ERROR (fatal)
FEW->>WPV: zone.run → emitIssue → handlePlaybackIssue
Note over FPC: channel zap / ngOnDestroy
FPC->>FS: stopFerriteSession(player)
FS->>FP: player.destroy() (outside zone)
Note over FP: releases OffscreenCanvas, terminates workers, clears all listeners
|
| import { | ||
| ChangeDetectionStrategy, | ||
| Component, | ||
| computed, | ||
| effect, | ||
| ElementRef, | ||
| HostListener, | ||
| inject, | ||
| input, | ||
| NgZone, |
There was a problem hiding this comment.
File size approaching hard limit
ferrite-player.component.ts is 346 lines. The project's TypeScript file-size rule (from CLAUDE.md) sets a soft limit of 300 lines and a hard maximum of 350–400 lines. This file is already 46 lines over the soft limit. The existing split into ferrite-session.ts, ferrite-event-wiring.ts, and overlay-interactions.ts is good, but the remaining volume in the component itself (event-handler methods, the fullscreen host-listener, applyVolume, playChannel, and teardown) could be extracted into a small FerritePlayerStateService or a dedicated ferrite-player.actions.ts helper if a follow-up feature adds more methods.
Context Used: CLAUDE.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: libs/ui/playback/src/lib/ferrite-player/ferrite-player.component.ts
Line: 1-10
Comment:
**File size approaching hard limit**
`ferrite-player.component.ts` is 346 lines. The project's TypeScript file-size rule (from `CLAUDE.md`) sets a soft limit of 300 lines and a hard maximum of 350–400 lines. This file is already 46 lines over the soft limit. The existing split into `ferrite-session.ts`, `ferrite-event-wiring.ts`, and `overlay-interactions.ts` is good, but the remaining volume in the component itself (event-handler methods, the fullscreen host-listener, `applyVolume`, `playChannel`, and teardown) could be extracted into a small `FerritePlayerStateService` or a dedicated `ferrite-player.actions.ts` helper if a follow-up feature adds more methods.
**Context Used:** CLAUDE.md ([source](https://app.greptile.com/iptvnator/github/4gray/iptvnator/-/custom-context?memory=d7399d0f-9d03-4561-9aed-4525c6d78ad1))
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| "buildTarget": "web:build:development" | ||
| }, | ||
| "ferrite": { | ||
| "buildTarget": "web:build:production", | ||
| "host": "0.0.0.0", | ||
| "allowedHosts": true, | ||
| "headers": { | ||
| "Cross-Origin-Opener-Policy": "same-origin", | ||
| "Cross-Origin-Embedder-Policy": "credentialless" | ||
| } | ||
| } |
There was a problem hiding this comment.
ferrite serve configuration rebuilds with the production target
"buildTarget": "web:build:production" means nx serve web --configuration=ferrite performs a full production build (full optimization, no source maps by default) before serving, making development iteration significantly slower. Unless the production build is specifically required for the COOP/COEP test (e.g. to validate the bundled worker assets), consider using "web:build:development" to keep fast rebuilds. The headers block that sets COOP/COEP is independent of the build target and will work with either.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/project.json
Line: 162-172
Comment:
**`ferrite` serve configuration rebuilds with the production target**
`"buildTarget": "web:build:production"` means `nx serve web --configuration=ferrite` performs a full production build (full optimization, no source maps by default) before serving, making development iteration significantly slower. Unless the production build is specifically required for the COOP/COEP test (e.g. to validate the bundled worker assets), consider using `"web:build:development"` to keep fast rebuilds. The `headers` block that sets COOP/COEP is independent of the build target and will work with either.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
… faster ferrite dev-serve - web-player-view: add the 'ferrite' arm to formatPlayer so the diagnostics detail panel shows "Ferrite" instead of a blank label when a Ferrite playback issue is surfaced. - project.json: the opt-in `ferrite` dev-serve config now builds web:build:development (was :production) — faster local iteration and the correct baseHref="/" for serving; the COOP/COEP headers live on the serve config, so isolation is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review! Addressed both cosmetic findings in the latest commit:
(Note: greptile only reviews on PR open, so this push won't re-trigger it — flagging the fixes here for the manual pass.) |
Ferrite (WASM/WebCodecs) inline player for software HEVC
Adds Ferrite as a new inline web player — a
<canvas>+ WebAssembly software decoder with an automatic WebCodecs hardware tier — so the web/PWA build can play HEVC where the browser has no hardware decoder (and H.264/MPEG-2, 8/10-bit, interlaced). Requires a cross-origin-isolated context (COOP/COEP); falls back to the default inline player for HLS (.m3u/.m3u8).What's in it
ferrite-playercomponent + session/event wiring (live + VOD), IPTVnator-owned Material controls and a long-press diagnostic overlay. Teardown goes throughdestroy()only.ferrite.{mjs,wasm},worker.js,present-worker.js) intoapps/webassets, plus an opt-in COOP/COEP isolated dev-serve configuration (nx serve web --configuration=ferrite).New in this revision (ferrite.js 1.3.2)
ferrite.js^1.3.0→^1.3.2. The 1.3.2 audio-path overhaul (clean 5.1→stereo downmix + device-rate resample, loudness leveling), all-codec anamorphic SAR, and correct HEVC sizing on browsers with bogusVideoFramedims (Edge) are internal to ferrite.js and apply automatically via the re-bundled engine.setDrc(). Always shown (audio is always decoded on both tiers); resets to Line per stream like the deint mode. Night is a built-in compressor that tames loud peaks on any codec — useful for late-night listening.Deliberately left out (no public ferrite.js API / not appropriate as app settings): the fast non-conformant decode toggle and a persisted default-deinterlace setting.
Validation
pnpm nx lint ui-playback✓pnpm nx test ui-playback✓ (140 tests; +2 Dyna control tests)pnpm nx test workspace-shell-feature✓ (command-palette registration)pnpm nx build web✓ (1.3.2 engine assets bundled intodist/apps/web/assets)