mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
beb62db3141a9dbacb9e6a7deb3fb1689b36e1ac
156
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
beb62db314 |
feat(settings): add shared web player controls toggle (#1198)
* docs(playback): design shared controls setting * docs(playback): plan shared controls setting * feat(settings): persist shared web controls preference * test(settings): harden shared controls normalization coverage * feat(settings): expose shared web controls toggle * fix(settings): label shared controls toggle * feat(playback): resolve shared controls from settings * test(playback): cover shared controls setting * docs(playback): document shared controls preference * fix(playback): await settings before host creation * fix(settings): normalize shared controls updates |
||
|
|
48e3736460 |
feat(artplayer): add feature-flagged shared controls (#1196)
* feat(artplayer): add feature-flagged shared controls Co-authored-by: Lars Emig <lars.emig@pickware.de> * fix(artplayer): align native type and signal inputs --------- Co-authored-by: Lars Emig <lars.emig@pickware.de> |
||
|
|
4e572c60ca |
feat(videojs): add feature-flagged shared controls (#1195)
* feat(videojs): add feature-flagged shared controls Rebuild the Video.js shared-controls integration on the current player lifecycle with Tech rebinds, source-scoped tracks, reset ordering, volume preservation, diagnostics gating, and default-off compatibility. Credits and supersedes the stacked implementation proposed in #1153. Co-authored-by: Lars Emig <lars.emig@pickware.de> * fix(videojs): harden MPEG-TS reset lifecycle --------- Co-authored-by: Lars Emig <lars.emig@pickware.de> |
||
|
|
c49ea2f6a8 |
feat(html-player): add feature-flagged shared controls (#1194)
* feat(html-player): bridge engine state to shared controls * fix(html-player): avoid HLS subtitle event reentry * fix(html-player): restore delayed HLS default subtitles * refactor(html-player): split controls bridge collaborators * feat(html-player): add feature-flagged shared controls * test(html-player): cover shared-controls source ownership * feat(html-player): pass shared-controls playback metadata * docs(player-controls): describe HTML5 shared-controls bridge * docs(player-controls): clarify HTML5 rollout effect * fix(html-player): reveal diagnostics from fullscreen * fix(html-player): defer HLS event resolution * refactor(html-player): isolate video element session |
||
|
|
f611ea3d7c |
feat(embedded-mpv): use shared controls for frame-copy (#1193)
* docs(embedded-mpv): plan frame-copy shared controls * feat(embedded-mpv): adapt frame-copy sessions to shared controls * fix(embedded-mpv): correlate recording control updates * fix(embedded-mpv): accept recording ack before command resolve * fix(embedded-mpv): serialize delayed recording commands * fix(embedded-mpv): latch buffered recording outcomes * feat(embedded-mpv): use shared controls for frame-copy * fix(embedded-mpv): isolate recording ticks by engine * fix(embedded-mpv): reset controls on engine handoff * docs(embedded-mpv): document frame-copy shared controls * docs(embedded-mpv): normalize shared-controls plans * fix(embedded-mpv): isolate legacy feedback on handoff * fix(player-controls): block toggles while stalled * refactor(embedded-mpv): isolate controls timing * fix(player-controls): reset recording feedback on handoff * fix(embedded-mpv): preserve newer session snapshots |
||
|
|
8f597b44cf |
refactor(embedded-mpv): split session controller into focused collaborators [2/7] (#1149)
* refactor(embedded-mpv): split session controller into focused collaborators Mechanical decomposition of the embedded-MPV session controller into focused collaborators under embedded-mpv-player/: - embedded-mpv-command-runner.ts: transport/track/recording IPC delegators with guarded snapshot reconciliation - embedded-mpv-session-factory.ts: pure placeholder-session factories (loading/attaching/error) and the startup-paint wait - embedded-mpv-stalled-tracker.ts: loading-stall timer and stalled flag - embedded-mpv-compositor.ts: host bounds measurement (measureBounds), re-exported from embedded-mpv-format.utils for existing imports No behavior change. The existing embedded-mpv-player component is kept untouched and keeps working against the controller's unchanged public API (commands are now bound fields delegating to the runner). Test coverage extended per Codecov patch report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(embedded-mpv): drop superseded overlay hooks * fix(embedded-mpv): guard async session races * docs(embedded-mpv): document renderer collaborators * fix(embedded-mpv): abort stale recording startup * docs(embedded-mpv): clarify renderer safety details --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: 4gray <serega05@gmail.com> |
||
|
|
aa6ee85d3f |
feat(player-controls): add shared engine-agnostic controls layer (#1148)
* feat(player-controls): shared engine-agnostic controls layer (flag off) Introduce a shared, engine-agnostic player-controls layer in libs/ui/playback as pure additive library code with no consumers yet. - Contract (player-controls.model.ts): PlayerControlsCapabilities, PlayerControlsState, and PlayerControlsCommands make up the PlayerController interface every engine adapter implements. - Single presentation component (app-player-controls): one controls UI binding purely to a PlayerController, with focused helpers for visibility auto-hide, volume, fullscreen (built-in DOM path), menus, keyboard shortcuts, seek/volume feedback, and the controls surface. - Web-video adapter (web-video-controls.adapter.ts + host directive): drives the contract from an HTMLVideoElement, including optional HLS.js quality/audio-track integration and series episode navigation. - Feature flag WEB_PLAYER_SHARED_CONTROLS (web-player-controls.flag.ts) defaults to OFF; no player component consumes the new layer yet, so runtime behavior is unchanged. - docs/architecture/player-controls-contract.md documents the target architecture (later PRs add the embedded-MPV adapter, immersive overlay, and host-supplied fullscreen delegate). Review-driven hardening: the web-video adapter now holds the host series-navigation signal reactively (updates after setContext are reflected); the shared controls template is fully localized via ngx-translate (reusing the EMBEDDED_MPV.PLAYER.* keys); single click on the viewport toggles play/pause deferred so a double-click still fullscreens; keyboard shortcuts are shadow-DOM-safe (composedPath) and guard against duplicate-instance double-execution (defaultPrevented); and hidden controls now also disable shortcuts. Test coverage extended per Codecov patch report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(player-controls): harden shared controls foundation * fix(player-controls): restore detached adapter state * fix(player-controls): harden keyboard and adapter state * fix(player-controls): refresh readiness and hide timers * fix(player-controls): keep controls root inside surface * fix(player-controls): hide seek controls for live streams * fix(player-controls): harden multi-engine control state * fix(player-controls): respect runtime interaction availability * fix(player-controls): gate loading toggles and localize mute * fix(player-controls): harden surface and error states * fix(player-controls): preserve volume and cursor state --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: 4gray <serega05@gmail.com> |
||
|
|
59e08fd2d6 |
feat(embedded-mpv): add Windows frame-copy support (#1175)
Port the embedded mpv frame-copy pipeline to Windows with WGL rendering and named shared memory. Includes packaging validation, platform gates, tests, and architecture documentation. |
||
|
|
7d75d989e8 |
feat(embedded-mpv): Linux port of the frame-copy rendering engine (headless EGL) (#1171)
* feat(embedded-mpv): Linux frame-copy helper via headless EGL Port the frame-copy engine's native layer to Linux (PORTING.md items 1-4): - frame_helper_gl.h: platform GlContext abstraction. macOS keeps the CGL path (moved verbatim); Linux acquires an EGL display in order surfaceless-Mesa -> default display -> GBM render node, binds a 3.2 core desktop-GL context surfaceless (1x1 pbuffer fallback), and hands mpv eglGetProcAddress. The helper's own GL calls link against glvnd libOpenGL, so no display server is required. - frame_shm.h: portable frame_shm_now_ns() (CLOCK_MONOTONIC) shared by the helper and the reader addon, replacing the macOS-only clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW); producer and consumer stay on the same clock. - embedded_mpv_frame_reader.c: real implementation now also on __linux__ (the code was already POSIX apart from the clock call). - binding.gyp: OS==linux executable branch for iptvnator_mpv_helper linking system libmpv (-lmpv) + EGL/OpenGL/gbm, with rpaths for $ORIGIN/lib and the build-time library dir. The in-process addon still does not link libmpv - the ban only binds in-process, the helper is out of process. - build-embedded-mpv.js: system-dev fallback on Linux (LIBMPV_INCLUDE_DIR or /usr/include) so a distro libmpv-dev install builds without staging a vendored runtime; a pre-set LINUX_NATIVE_LIBRARY_DIR now wins over the vendored lib dir. Verified on Ubuntu 25.04 / i7-1165G7 (Iris Xe): lavfi smoke per PORTING.md (idle->loading->playing snapshots at 4 Hz, aspect-fit generation bump g1 1280x720 -> g2 960x720 for a 4:3 source), reader probe 60 fps at 1080p60 with 0 torn reads, clean quit with no leaked processes or shm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-mpv): enable the frame-copy engine gates on Linux Flip the TypeScript side of the Linux port (PORTING.md item 5). A shared dependency-free predicate, isFrameCopyPlatformSupported() (linux any-arch, darwin arm64-only), now backs all four gates so they cannot drift: - main.ts: the persisted Settings toggle promotes to the env flag on Linux too (this runs before window creation and controls the sandbox relax). - EmbeddedMpvNativeService.isFrameCopyEngineActive/isFrameCopyAvailable. - EmbeddedMpvFrameCopyAdapter.isSupported. getSupport() ordering: the frame-copy branch moves above the Linux-only native-engine prerequisites - the X11/Xwayland display-server check and the system-mpv-on-PATH probe only bind the --wid native engine, while the frame-copy helper renders offscreen (headless EGL) and links libmpv itself. createSession() also skips resolving the native window handle for frame-copy sessions, which the adapter ignores anyway, so native-Wayland sessions no longer trip the window-handle assertion. Settings copy: the i18n frame-copy description now says macOS (Apple Silicon) and Linux in all 18 languages; stale macOS-only doc comments in the settings/support interfaces updated alongside. Tests: platform-gate matrix for the adapter (darwin arm64/x64, linux x64/arm64, win32) and service specs covering Linux activation under native Wayland, macOS arm64 staying active, macOS x64 staying native, and the skipped window handle for frame-copy sessions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(packaging): CI + package guards for the Linux frame-copy helper - build-and-make.yaml: install libegl-dev/libgl-dev/libgbm-dev on the Linux runner (the helper's EGL backend needs them now that the helper target builds on Linux), and verify the built helper exists and DOES link libmpv - the inverse of the addon's no-libmpv rule, which still holds and stays validated. - electron-after-pack.cjs: strip iptvnator_mpv_helper from packaged Linux apps. It links the build host's system libmpv, which end-user systems cannot be assumed to have; the support probe treats the missing helper as frame-copy-unavailable (dev-build-only engine until the bundled-runtime staging milestone). - frame_helper_gl.h: log the chosen EGL display tier to stderr (the adapter mirrors helper stderr), so bring-up problems on exotic setups are diagnosable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(embedded-mpv): document the Linux frame-copy port - architecture doc: frame-copy section covers Linux (EGL display tiers, build deps, package strip), Linux support matrix notes the frame-copy exception to the X11 + system-mpv requirements, Linux measured baseline. - RESULTS.md: Ubuntu 25.04 / i7-1165G7 (Iris Xe) measurement rows via the production helper + reader probe; viewport-size claim reproduced. - PORTING.md: Linux marked done with pointers to what changed; Windows remains the open port and its perf gate the open decision. - CLAUDE.md + tools/embedded-mpv/README.md: platform scope, Linux dev build requirements, system-headers fallback, helper strip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(embedded-mpv): commit the Linux frame-copy measurement probe linux-frame-probe.mjs reproduces the RESULTS.md Linux rows: spawns the production helper, attaches the frame-reader addon to the announced shm generation, and reports new-frame fps, copy wall time, produce->copy age, torn reads and pixel spread. Usage documented in RESULTS.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): address multi-agent review findings on the Linux port Confirmed findings (each verified by 3 adversarial reviewers): - CI would fail to link the helper: -lOpenGL needs the unversioned glvnd libOpenGL.so, shipped only by libopengl-dev, which neither the runner images nor the previous apt line provide. Added to the workflow and to every documented Linux build-dep list. - The new 'test -x' dist guard could never pass: webpack's dist asset copy drops file modes (helper arrives as 0644). The guard is now 'test -f'; electron-after-pack.cjs restores the execute bit on packaged helpers (also fixes packaged-macOS spawns); the support probe now requires X_OK, so a mode-stripped helper reads as frame-copy-unavailable and falls back to native instead of failing spawn with EACCES. - The Settings frame-copy toggle was unreachable in exactly the Linux states the port targets: the native-Wayland and missing-system-mpv unsupported payloads omitted frameCopyAvailable, and toggle visibility derives solely from it. Both returns now advertise availability. Also from review: - build-embedded-mpv.js keeps the old graceful-skip contract when the new system-dev fallback finds libmpv-dev but the GL/EGL/gbm dev stack is missing (previously such machines skipped; a hard electron-build failure was a regression). - createSession derives the window-handle skip from the dispatched addon instead of re-evaluating the engine gate, so the two cannot disagree. - The render thread logs the GL renderer string (surfaceless Mesa can silently pick llvmpipe on non-Mesa-primary systems; now diagnosable — verified 'Mesa Intel Iris Xe' on this machine). - Specs pin the new semantics: frameCopyAvailable advertised while native is unsupported (Wayland / no mpv), frame-copy supported without a system mpv, and the handle-skip test disposes its session through the owning adapter. - Docs: PORTING.md file map reflects the frame_helper_gl.h seam for the Windows porter; helper-strip removal correctly gated on milestone 4 (bundled libmpv), not milestone 3; RESULTS.md preamble notes the RAW->MONOTONIC clock change; stale '(macOS)' scope comments updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): address Greptile/Codex review comments - Sandbox gate requires a usable helper (Greptile P1, security): the main.ts env promotion now also probes for an executable iptvnator_mpv_helper before relaxing the window sandbox — a stale opt-in on packaged Linux (helper deliberately stripped) or after a cleaned native build no longer costs a sandboxless launch for an engine that cannot activate. Helper discovery (addon candidate paths + X_OK probe) moved into embedded-mpv-frame-copy-platform.util.ts, shared by main.ts and the service; the service keeps thin instance wrappers so tests can stub per scenario. New util spec pins the platform matrix, candidate resolution, and the execute-bit semantics. - Stale frame-copy artifacts on skipped builds (Codex P2): cleanOutput() now also removes iptvnator_mpv_helper and embedded_mpv_frame_reader.node, so a failed/skipped rebuild cannot leave a previous helper advertising frame-copy support against a runtime the build just declared unavailable. - Multiarch default lib dir (Greptile P1, partially refuted): -l resolution never depended on our -L (the compiler's built-in search paths include the Debian/Ubuntu multiarch dir — proven by the green CI run linking with a nonexistent -L dir), but the system-dev fallback now defaults to /usr/lib/<multiarch-triple> when present so the -L flag and the helper's baked rpath point somewhere real. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): harden Linux frame-copy port --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
26271fc076 |
feat(embedded-mpv): frame-copy rendering engine (experimental, macOS Apple Silicon) (#1169)
* spike(embedded-mpv): frame-copy pipeline prototype (helper + shm ring + Electron viewer) Standalone macOS spike for the frame-copy unification direction from the 2026-07-10 analysis: a helper process renders mpv offscreen into a GL FBO, reads frames back through an async PBO ring, and publishes BGRA frames into a 3-slot POSIX shm seqlock ring; a minimal Electron viewer copies the newest frame via a plain-C N-API addon and uploads it to a WebGL canvas per rAF. First numbers on M1 Pro (see spike README): 4K60 HEVC hwdec sustained at 60 fps end to end, ~1.2 ms shm copy + ~3.5 ms texture upload, ~10 ms produce-to-upload age, zero torn frames. Remaining gates: weak hardware, long-run pacing, HDR, latency flash test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * spike(embedded-mpv): add RESULTS.md measurement log with M1 Pro baseline Structured per-machine table with repro commands so the pending Intel Mac and Windows iGPU runs can be appended and compared one-to-one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * spike(embedded-mpv): pacing/judder instrumentation + 50/25 fps and HDR gate results Viewer now measures inter-frame intervals on both clocks (present side and producer side): stddev/p99/max, late-frame counters vs the producer's median interval, and a cumulative LONGRUN summary every 30 s. The addon exposes the producer timestamp (produceMs) for this. Measured on M1 Pro: 50 fps and 25 fps cadences are clean (late frames only at startup; residual jitter is 120 Hz rAF grid quantization, bounded by one display tick), and 4K25 HDR10 PQ/BT.2020 is tonemapped to SDR by mpv before readback at full rate with unchanged copy costs. RESULTS.md carries the tables and HDR-clip repro commands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * spike(embedded-mpv): record 10-minute 4K60 HEVC long-run results Zero dropped frames and zero torn reads after the first-minute warmup over ~8.5 minutes; steady-state late frames (~0.4%) track the 12 s test clip's --loop restarts, not the copy pipeline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * spike(embedded-mpv): viewport-scaling measurement + integration design draft Confirms the render-at-viewport-size claim (4K source in a 720p FBO costs 720p: 0.17 ms readback / 0.16 ms copy / 0.17 ms upload at 60 fps) and adds DESIGN.md — the draft integration architecture: per-session helper process linking bundled libmpv on all platforms (finally full-featured + Wayland- agnostic Linux), JSON-over-stdio control evolving the Linux wid protocol, unchanged EmbeddedMpvSession renderer contract, shm generations for resize, packaging via the existing vendored-runtime tooling, rollout behind its own flag with the docked path as default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * spike(embedded-mpv): auto-detect Homebrew prefix and Node headers for Intel Macs BREW_PREFIX was hardcoded to /opt/homebrew (Apple Silicon) and NODE_INC to one nvm version; both now resolve via brew --prefix and the PATH node's execPath, so the pending Intel Mac run needs no Makefile edits. README gets a fresh-machine checklist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * spike(embedded-mpv): self-contained measurement bundle for machines without Node/pnpm make-bundle.sh assembles a tarball with the spike sources, vendored N-API headers (Makefile prefers them when present, so no Node install is needed), pre-generated 4K HEVC/HDR10 test clips, and an official Electron dist download for the target arch. collect-results.sh builds and runs the full RESULTS.md scenario suite automatically (plus an optional --long 10-minute run) and writes one results-<host>-<date>.txt to send back. Target-machine prerequisites shrink to Xcode CLT + brew mpv — built for the pending Intel Mac baseline run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * spike(embedded-mpv): support MacPorts libmpv and legacy-macOS bundles Makefile and collect-results.sh now detect libmpv in the Homebrew prefix or MacPorts /opt/local (Homebrew is unsupported on legacy macOS like High Sierra; 'sudo port install mpv +libmpv' provides libmpv there). make-bundle takes ELECTRON_VERSION/BUNDLE_SUFFIX overrides — Electron 27+ needs macOS 10.15, so High Sierra bundles ship Electron 26.6.10 (LSMinimumSystemVersion 10.13). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * spike(embedded-mpv): scope macOS frame-copy engine to Apple Silicon only Owner decision 2026-07-10: skip Intel Mac measurements and gate the future frame-copy engine on arm64. Intel Macs able to run the app at all are a shrinking 2015-2020 cohort and keep the docked/external/web player paths; the macOS hardware gate closes with the M1 Pro numbers, and remaining hardware risk moves to the Windows/Linux ports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-mpv): frame-copy helper process and shm frame reader (native layer) iptvnator_mpv_helper: one-process-per-session libmpv host that renders offscreen at viewport size (headless CGL + async PBO ring, validated in spikes/mpv-frame-copy), publishes BGRA frames into a seqlock shm ring with resize generations, plays audio directly, and speaks a stdio protocol — tab-separated commands in, JSON events out. The snapshot event mirrors NativeEmbeddedMpvSessionSnapshot; status semantics (END_FILE reasons, eof-reached with keep-open, pause gated on loaded path, fatal-only status flips) are ported from embedded_mpv.mm. embedded_mpv_frame_reader.node: plain-C N-API reader the preload script uses to memcpy the newest complete frame into a V8 ArrayBuffer (Electron's memory cage forbids zero-copy). Stub exports off macOS. Both build as extra binding.gyp targets through build-embedded-mpv.js; the helper gets the same libmpv dependency-path rewrite + ad-hoc re-sign as the addon and is validated by the forbidden-link check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-mpv): frame-copy engine wiring in main process and preload EmbeddedMpvFrameCopyAdapter implements the NativeEmbeddedMpvAddon surface over a per-session helper process (spawn, stdio protocol, snapshot cache, graceful quit->SIGTERM->SIGKILL teardown), so EmbeddedMpvNativeService reuses its polling/diff/power-blocker/recording logic unchanged. The IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY flag (darwin/arm64 only) routes getAddon() to the adapter and reports engine: 'frame-copy' in support. The preload frame pump loads the shm reader addon, copies the newest frame once per rAF into a reused buffer, and uploads it to WebGL2 on the renderer's canvas — no frame data crosses the contextBridge; the bridge only gains attachEmbeddedMpvFrameView/detachEmbeddedMpvFrameView. The experiment flag relaxes the window sandbox for that native require; contextIsolation and nodeIntegration:false stay on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-mpv): frame-copy canvas mode in the player component + docs EmbeddedMpvPlayerComponent renders <canvas data-embedded-mpv-frame> when support reports engine 'frame-copy' and the session controller starts/stops the preload frame pump around the session lifecycle. The bounds provider skips HIDDEN_BOUNDS and the popover cutout for this engine — the canvas is ordinary DOM, dialogs and popovers stack above it natively; bounds sync still drives the helper's render size. Adapter unit tests cover spawn args, snapshot caching, shm generations, protocol encoding, unexpected-exit mapping, and dispose escalation. Architecture doc and CLAUDE.md describe the engine, its flag, and the sandbox trade-off. Verified end to end in the built app (M1 Pro): engine detection, helper spawn, lavfi playback onto the canvas via CDP-injected smoke — including an orientation fix (helper FLIP_Y already yields texture-order rows; the pump shader must not flip uv again). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): close helper stdin on dispose + lifecycle logging Live testing surfaced a stray idle helper that survived a session switch; until the root cause is pinned down, dispose now also closes the child's stdin (the helper exits on EOF) as a second kill path besides quit -> SIGTERM -> SIGKILL, and spawn/dispose/exit are logged with the session id so leaks are attributable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): reap sessions when the renderer reloads or crashes Root cause of the stray idle helper found during live testing: session teardown lives in the renderer's Angular lifecycle, which never runs on a renderer crash or hard reload — the main process kept the session (and its frame-copy helper process / native mpv handle) alive until app shutdown. EmbeddedMpvNativeService now watches the main window's webContents for render-process-gone and did-navigate (full reloads only; in-app Angular routing emits did-navigate-in-page) and disposes every session. Applies to both engines. Verified live: location.reload() during frame-copy playback logs 'Disposing 1 session(s): renderer reloaded' and the helper exits cleanly. Regression test drives both events against the service. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-mpv): Settings toggle for the frame-copy engine New 'Embedded MPV: frame-copy engine' checkbox in Settings > Playback, shown only when the machine can run it (macOS arm64 with the helper binary present — support now reports frameCopyAvailable). The choice persists to the main-process config store because the engine relaxes the window sandbox for the preload frame pump, which is fixed at window creation: main.ts reads the store before creating the window and sets the engine env var; an explicitly set env var (including '0') always wins, and the UI shows a restart hint while the saved choice differs from the active engine. Localized in all 18 locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-mpv): aspect-fit rendering in the frame-copy helper The helper now observes dwidth/dheight and renders its FBO at the aspect-fit size of the video inside the requested viewport, bumping a shm generation on change — letterbox bars are never baked into frames (the VOD watch shell's ~2:1 box no longer shows black side bars; the canvas background is transparent so the sides show the app surface, while fullscreen keeps its black backdrop). Frames also get smaller than the viewport when aspects differ, trimming copy cost. Aspect override changes refit automatically. Snapshots now carry videoWidth/videoHeight, and the adapter forwards IPTVNATOR_EMBEDDED_MPV_AUDIO_DELAY to mpv's audio-delay for lip-sync tuning until proper calibration lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-mpv): require frame-copy artifacts in macOS package validation + docs macOS packages that ship embedded_mpv.node must also ship the iptvnator_mpv_helper binary and the embedded_mpv_frame_reader.node addon — they come out of the same binding.gyp run, and a package missing them would silently lose the frame-copy engine. Covered in the package-identity test. Architecture doc and CLAUDE.md document the Settings toggle, aspect-fit rendering, audio-delay passthrough, and the renderer-reload session reaping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(electron): inline TS helpers so the sandboxed preload keeps working The frame pump's async/await (target es2015 + importHelpers) made webpack externalize tslib in main.preload.js. Sandboxed preloads can only require Electron's built-in module whitelist, so the entire preload script failed to load and window.electron disappeared for every run without the frame-copy flag. importHelpers:false for electron-backend keeps the preload bundle self-contained — and future async code in preload can no longer silently reintroduce the breakage. Verified live: sandboxed run now has the bridge, reports engine 'native' and frameCopyAvailable true. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(embedded-mpv): commit the frame-copy analysis handoff + source inventory The 2026-07-10 analysis that led to this branch now lives next to the spike (spikes/mpv-frame-copy/ANALYSIS.md), and the architecture doc's What To Commit section lists the frame-copy engine sources. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): address review findings on the frame-copy engine - Stale pump attach can no longer win over a newer session: attach/detach bump a shared epoch and async attach waits re-check it after every await, so an attach for a replaced session aborts instead of installing itself (greptile P1). - A failed frame-view attach (no canvas, no WebGL2, reader missing) now disposes the session and surfaces the error UI instead of leaving audio playing behind a black canvas (codex P2). - A stale frame-copy opt-in without the helper binary falls back to the native engine instead of reporting embedded MPV unsupported, and the Settings checkbox stays visible while a saved opt-in exists so it can always be cleared (codex P2). Regression test covers the fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(packaging): make the darwin frame-copy packaging test host-agnostic On non-macOS CI hosts validatePackagedEmbeddedMpv also reports that macOS link validation needs a macOS host, so the success-path assertion now checks only the frame-copy artifact requirement instead of expecting an empty error list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(embedded-mpv): Windows/Linux porting handoff for the frame-copy engine Self-contained entry point for porting sessions on other machines: current state and coordination constraints, per-OS task lists (Linux EGL first, then Windows WGL + named shm — the decisive iGPU perf gate), the hard-won gotchas from the macOS integration (preload/tslib sandbox breakage, V8 memory cage, frame orientation, stale-attach epoch, dispose escalation, node-gyp naming, snapshot protocol semantics), testing recipes, and the suggested milestone order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(embedded-mpv): branching and merge strategy in the porting handoff Port work goes to stacked branches off the frame-copy branch (PR base = frame-copy branch, sequential merges, stack depth one), never into the frozen PR #1169 branch itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(embedded-mpv): drop stale uncommitted note from porting handoff Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): harden frame-copy helper startup --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
54265755ee |
fix(favorites): persist custom drag-and-drop order for Xtream favorites (#1143)
* fix(favorites): persist custom drag-and-drop order for Xtream favorites
Prepared-statement writes dispatched via drizzle's `.execute()` on the
better-sqlite3 driver return a promise and defer the write to a microtask.
Inside a synchronous `db.transaction(() => ...)` callback (which cannot
await), the transaction commits before that promise settles, so the write
is a silent no-op — no error, no rows changed.
This bit `reorderGlobalFavorites`: the custom favorites order never
persisted for the per-playlist ("This playlist") Xtream scope, which relies
solely on the `favorites.position` column. The global ("All playlists")
scope masked the bug because it also persists an order to the `appState`
`global-favorites-channel-order-v1` key and re-applies it on read.
`removeRecentItemsBatch` had the same latent bug — batch "clear recent
items" silently did nothing.
Switch both writers to synchronous `.run()`. Add regression coverage that
asserts `.run()` (not `.execute()`) is used and would fail on the old
behavior, and document the gotcha in the DB worker architecture doc.
Verified over CDP against a live Electron instance: reorder writes
positions 0..N, and the order survives navigation and a full reload.
Fixes #1137
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(favorites): scope reorder position writes by playlist
The global favorites reorder wrote the new position filtering only by
content_id, so two Xtream playlists holding a favorite with the same
content_id would clobber each other's persisted order (greptile P1).
Thread playlist_id through the whole reorder path — the renderer builder
(UnifiedCollectionItem already carries playlistId), the IPC contract
(ElectronBridgeFavoriteReorderUpdate + inline payload types), the worker
op — and scope the prepared UPDATE by (contentId, playlistId), matching
the favorites composite unique index.
Tests: favorites.operations.spec asserts the playlistId placeholder and
per-row playlistId payload; preload contract fixture updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(favorites): include playlist_id in workspace global favorites reorder payload
The workspace global-favorites reorder path still sent updates with only
content_id and position. Since the backend UPDATE is now scoped by
(contentId, playlistId), that payload binds an undefined playlist id and
matches no rows — the DB write silently no-ops (flagged by Greptile P1).
Also scope the prepared-statement example in the sqlite-db-worker gotcha
doc by (contentId, playlistId) so it no longer documents the
cross-playlist rewrite this PR fixes (flagged by Codex P3).
Regression spec asserts the reorder payload carries playlist_id per item
(fails on the old payload shape) and that the appState uid order is
still persisted for non-Xtream items.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
f856226dc3 |
fix(xtream): send player-style User-Agent to avoid WAF challenge blocking connections (#1170)
Some Xtream panels sit behind Cloudflare/WAF rules that challenge generic browser-looking User-Agents while allowlisting known IPTV player clients. The previous hardcoded, truncated browser-style User-Agent in xtream.events.ts (XTREAM_REQUEST and XTREAM_PROBE_URL) was being served a Cloudflare challenge page (HTTP 403 HTML) instead of the real API response, so "Test Connection" always failed with "Could not connect to the portal" even though the same portal worked fine via curl (with a player-style UA) and Safari. Switching to a shared VLC-style User-Agent constant across all three request sites resolves it, verified against a live panel. Co-authored-by: Sergio Herencias Redondo <sherencr@MacBook-Pro-de-diverzy.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
64839e3495 |
fix(tmdb): fall back to original language for missing trailers too (#1136)
* fix(tmdb): fall back to original language for missing trailers too TMDB language-filters videos the same way it filters overviews: a Russian-only title has its trailer tagged iso_639_1=ru, so an en-US request returns no trailer — the trailer only appeared when the user switched the app language. The original-language fallback previously triggered on an empty overview only, so titles that had a translated overview but a language-only trailer never got one. detailsFallbackLanguage now also triggers when the payload has no usable YouTube trailer, and fillDetailsFromFallback fills the overview and the trailer independently — a present app-language overview is kept while the trailer is pulled from the original-language payload. The fallback fetch stays best-effort and cached per language. Tests: extended tmdb-language-fallback.spec.ts (trailer-missing trigger, independent overview/trailer fill, YouTube-only guard). Docs updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(tmdb): cover combined overview+trailer fallback fill Adds the missing case where the primary payload lacks both overview and trailer and the fallback supplies both — the two fill branches are independent (greptile). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c55acfb1d9 |
fix(epg): wire catch-up Watch button in unified live tab, surface resolve failures (#1133)
* fix(epg): wire catch-up Watch button in unified live tab, surface resolve failures The favorites/recent live views (UnifiedLiveTabComponent) passed archivePlaybackAvailable/archiveDays to the EPG timeline and list view, so the "Watch" button and the dialog's "Watch from start" action rendered — but neither (programActivated) nor (returnToLive) was bound. Clicking Watch emitted an event nobody listened to: no error, no feedback, no playback change. - Handle programActivated in the unified live tab: resolve the catch-up URL via resolveM3uCatchupUrl, swap the inline player's playback target (or hand it to the configured external player), show the "Archive playback" summary label, "From archive" block tag and "Return to live"; clear the override on channel switch/close/return. - Never fail silently: both the unified tab and the m3u-state effect now show an EPG.TIMELINE.CATCHUP_FAILED snackbar when the replay URL can't be resolved. New i18n key translated into all 17 locales via the i18n-fill workflow. - Extract the effect's resolve branch into a pure resolveActiveEpgProgramAction util and the unified tab's EPG summary helpers into unified-live-epg-summary.util.ts (component shrinks 598 → 572 lines despite the new feature). - Regression coverage: 6 component tests for the catch-up flow, 3 unit tests for the resolver action; docs updated in m3u-playlist-module.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(epg): address PR review — untrack progress tick in archive summary, open live external without prior timeshift - liveEpgPanelSummary no longer tracks the 30s progressTick while an archived programme is shown (Greptile P2): the archive summary is frozen, so re-running the computed each tick only produced needless re-renders. - returnToLivePlayback no longer early-returns when no timeshift is active (Codex P2): with an external player configured and openStreamOnDoubleClick enabled, the programme dialog's "Watch live" action now launches the external player instead of being dropped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
862b271eac |
feat(tmdb): dashboard trending + hero enrichment, cross-portal Similar rail (#1132)
* feat(dashboard): TMDB trending rail and hero enrichment (backdrop, badges, S/E)
Two dashboard additions, both async after first paint so the page renders
exactly as fast as before:
Trending rail ("Trending this week", dashboardRails.tmdbTrending toggle,
default on, rendered only when TMDB is opted in AND the Electron DB
worker is available):
- TmdbTrendingService fetches /trending/{movie,tv}/week (one request
each, cached one day per language in tmdb_metadata under
trending:week), merges by popularity; exposed via the enrichment
facade (getTrendingWeek)
- DashboardTrendingService matches the titles against imported Xtream
playlists with ONE batched DB_MATCH_TITLES request, applying the same
two-tier + year-compatibility rule as actor pages; matched cards show
the playlist name and navigate straight to the detail view, unmatched
cards open the global search prefilled (?q=)
- The load fires only after the dashboard's own recent/favorites data
is in (never competes for the worker at startup) and once per session
- DashboardRailCard gained optional queryParams for the search links
Hero enrichment:
- DashboardHeroTmdbService patches the hero with a TMDB backdrop (only
when the item has none), a rating badge and up to two genre chips —
via the enrichment facade, so previously opened items resolve from
the SQLite cache without network; memoized per title per session,
staleness-guarded against hero changes in flight
- Series heroes show the tracked "S{n}·E{n}" badge from the playback
position; the watch-progress bar no longer applies to live heroes
Settings: new dashboardRails.tmdbTrending toggle in Settings > Dashboard.
i18n: 3 new keys translated into all 17 locales via tools/i18n patches.
Tests: dashboard-trending.service.spec.ts (gating, matching, year guard,
single-flight); settings fixtures updated. Docs updated
(tmdb-metadata-enrichment.md Dashboard Integration section, CLAUDE.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): resolve hero TMDB extras for Stalker embedded-series items
Stalker vclub items carry type 'movie' in activity rows but are TV shows
on TMDB, so the hero's movie lookup found no confident match and the
backdrop/badges never appeared — while the detail view (which resolves
via is_series) showed them. When a movie-typed hero item has no movie
match, retry the lookup as TV: the detail view has usually already
cached that resolution, and misses are negative-cached.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(tmdb): cross-portal "Similar" rail — Stalker gets it, Xtream gains other-portal matches
The Similar rail only existed on Xtream because it matched against the
locally loaded catalog; Stalker catalogs are server-paginated, so its
detail views had no rail despite tmdb_recommendations being cached.
New CrossPortalSimilarService (libs/services) matches recommendations
against ALL imported Xtream playlists with one batched DB_MATCH_TITLES
worker request — the same two-tier normalized-title + year-compatibility
rule as actor pages and the trending rail. Electron-only; resolves to []
in the PWA.
- Stalker: the shared VodDetailsComponent (movies; covers catalog and
inline detail hosts) and stalker-series-view (series) now render a
"Similar" rail from cross-portal matches, each card badged with the
source playlist and navigating into that portal's detail view.
- Xtream: vod/serial detail rails keep instant local-catalog matches and
append cross-portal matches (current playlist excluded, deduplicated
against local hits by normalized exact title), also playlist-badged.
- Loads async after the detail view renders, staleness-guarded; the
section only appears when there is something to show.
Tests: cross-portal-similar.service.spec.ts (PWA gate, navigation
targets, playlist exclusion, type/year guards). Docs updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tmdb): drop TestBed from cross-portal similar spec
The services Jest target has no @angular/core/testing (same CI failure
as the cache spec earlier) — construct the service via Injector.create +
runInInjectionContext instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): address PR review — reactive opt-out gates, retry after empty trending load
- Trending rail and hero TMDB extras now vanish immediately when the
TMDB opt-in is switched off mid-session: the render computeds read the
settings signal through isAvailable/isEnabled instead of trusting data
loaded earlier (Codex P2 ×2).
- loadedOnce latches only after a successful non-empty load, so a
transient TMDB outage on first visit no longer suppresses the rail for
the whole session — the next dashboard visit retries (greptile P2).
- Unified the duplicated heroTmdbExtras() read in the hero computed
(greptile P2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
9635f398c1 |
fix(tmdb): restore trailers in packaged builds, fall back to original language for missing text (#1130)
* fix(tmdb): restore trailers in packaged builds, fall back to original language for missing text Two production-only gaps reported against the packaged 0.22 build: 1. Trailers showed YouTube "Error 153 — Video player configuration error". YouTube requires a Referer on /embed requests; the packaged app loads the renderer from file://, which never sends one (dev works because the origin is localhost). The Electron main process now injects the project site as Referer for YouTube embed hosts when the header is absent (request-header-overrides.service.ts, registered at startup via registerStaticHeaderShims). Existing Referers are never overwritten, other hosts untouched. 2. Descriptions vanished for content whose original language differs from the app language: TMDB does not fall back on missing translations, so a Russian-only series has empty overviews in en-US. When the app-language payload carries no text, the enrichment refetches once in the content's original_language and fills ONLY the missing text fields (tmdb-language-fallback.ts): details overview, season overview and per-episode names/overviews. Genres, credits and artwork stay in the app language; both language rows share the cache. Tests: 3 new shim cases in request-header-overrides.service.spec.ts, new tmdb-language-fallback.spec.ts (9 cases). Docs updated (tmdb-metadata-enrichment.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): address PR review — best-effort fallback fetch, /embed/ scope, names-only trigger - A failing original-language fallback fetch no longer costs the already-fetched primary details payload (Codex P2) — it is wrapped best-effort and the primary payload is returned on error. - The YouTube Referer shim now scopes to /embed/ paths, so regular www.youtube.com requests keep their real (missing) Referer (greptile). - seasonNeedsTextFallback also triggers when episode overviews exist but ALL episode names are empty (partially translated seasons; greptile). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ce24e16e32 |
feat(portals): two-state VOD detail pages (browse ↔ watch) with season tabs (#1127)
* feat(ui): slim ContentHero to non-scrolling hero block The page scroll container moves out of the hero into the upcoming PortalDetailShellComponent; the hero no longer hosts a default content slot for player/seasons/extras. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): add PortalDetailShellComponent with browse/watch states Two-state detail layout shell: owns page scroll, hero collapse animation (~300ms, reduced-motion aware), Escape-to-close-player handling, and an About block that re-stamps host-provided *detailTags/*detailMeta templates in watch state. The [detail-player] slot is never wrapped in a shell conditional so the host-owned player subtree survives state changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(playback): restyle inline player header as now-playing bar Adds a back button (emits the existing closed output — in watch state back returns to browse, not route navigation) and replaces the hardcoded header strings with i18n keys (PORTALS.NOW_PLAYING, CLOSE_PLAYER, BACK_TO_BROWSE). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(xtream): migrate VOD and serial detail templates to detail shell Hero chips/meta/actions become *detailTags/*detailMeta/*detailActions templates; the inline player moves to the full-width [detail-player] slot; trailer and similar rail move to [detail-extras]. Escape and the now-playing back button close the inline player back to browse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(stalker): migrate shared VodDetails and series view to detail shell The shared VodDetailsComponent (Stalker VOD, collections, search) and StalkerSeriesViewComponent move their hero content into *detailTags/*detailMeta/*detailActions templates and host the inline player in the full-width [detail-player] slot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): season tabs replace two-level seasons navigation SeasonTabsComponent renders a pill row (dropdown beyond 6 seasons), an optional season description and a back-to-playing chip. The season container auto-selects a season (inline-playing episode's season → most recent in-progress → first), emits seasonSelected for auto-selections so lazy-load/enrichment hooks keep firing, highlights the inline-playing episode, and loses the seasons grid + 'Back to seasons' button. Download/progress helpers move to pure utils; season strings are now translatable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(portals): season descriptions under the season tabs Xtream reads season overviews from get_series_info; Stalker exposes the TMDB season overview through TmdbEnrichmentService.getSeason (same cache rows as the episode enrichment) and keys it per tmdbId so views reused across detail navigations cannot leak descriptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: document two-state detail layout and season tabs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): app-prefix detail template directive selectors, build fixes detailTags/detailMeta/detailActions → appDetailTags/appDetailMeta/ appDetailActions per @angular-eslint/directive-selector; type the Escape host listener as Event; drop a redundant ?? in season tabs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(i18n): translate new detail-page keys across all 17 locales 13 PORTALS.* keys (now-playing bar, About block, season tabs, episode empty states) translated via the i18n-fill workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): keep now-playing bar actions inside the viewport The nowrap title made the header grid track grow to min-content and pushed Copy/Close out of the page; min-width: 0 on the header/title row lets the title truncate instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): expandable season description and episode info dialog Season descriptions get the same show more/less toggle as the hero (new shared ExpandableTextComponent); episode cards and list rows get an info button that opens a dialog with the full plot, duration, air date and a Play action — card heights stay fixed and click-to-play semantics stay intact. Also: drop the per-frame backdrop blur on the player card and make the watch-enter scroll instant so the hero morph stops competing for frame budget. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): re-run TMDB season enrichment when the show match arrives With season tabs the first seasonSelected fires as soon as seasons load — usually before the async show-level TMDB enrichment has written tmdb_id — so the season fetch silently no-oped and was never retried, leaving episode stills/plots and the season description empty. Both detail hosts now key the fetch on (tmdb_id, selected season) in an effect, so it runs whichever arrives last. Also adds breathing room above the About divider in watch state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): move episode info button into the card body, compact overlay actions Three overlay buttons crowded the thumbnail on hover and the new info button had no styling at all. Info belongs with the content text: it now sits as a quiet ghost button at the end of the title row (revealed on card hover, MDC touch target clamped so it cannot swallow the play-on-click card area). Download + watched-toggle stay on the artwork, shrunk to 28px with a subtle edge border. List view already used the shared action style and is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): single watched indicator-toggle on episode cards Watched state was signalled three times on one card: the green badge, the hover toggle in the top-right cluster, and a checkmark appended to the title. Now one control does both jobs — a toggle at the top-left of the artwork that looks like the old green badge when watched (always visible) and appears as a ghost circle on hover when not. The right cluster keeps only the download action; the title checkmark is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(playback): back arrow in watch mode navigates straight to the list Leaving the theater took two clicks: back to browse, then back to the list. Watch state already shows everything browse offers (episodes, About, extras), so the bar's back arrow is now route-level back (new backClicked output wired to each host's goBack), while Close player and Escape keep exiting to browse without navigating. Replaces PORTALS.BACK_TO_BROWSE with a generic top-level BACK key (all locales). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): compact episode stills in the list view When a season has genuinely distinct per-episode art (TMDB stills or provider images), list rows show a 96x54 thumbnail with the episode number riding on it instead of the number square. When every episode repeats the same image (providers often send the series poster), the plain number square stays — a column of identical posters is worse than none. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(ui): compact list-thumbnail spec under the max-lines limit Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): address Greptile review — real fallback asset, untracked overflow measure default-episode.png never existed (pre-existing in the grid card, copied into the list thumbs) — both onerror handlers now fall back to the real default-poster.png. The expandable-text and content-hero measuring effects read the expanded signal via untracked so toggling show more/less no longer rebuilds their ResizeObservers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
865a44c874 | refactor(tmdb): split enrichment services, extract Stalker season overlay, cap PWA cache (#1126) | ||
|
|
dc05a2566e |
feat(tmdb): opt-in TMDB metadata enrichment for Xtream and Stalker portals (#1123)
* feat(tmdb): opt-in TMDB metadata enrichment for Xtream and Stalker portals Adds an opt-in TMDB integration (Settings > Metadata) that enriches detail views with a field-level merge — the provider stays authoritative for stream data, TMDB fills editorial fields when the match is confident. Enrichment: - Movie/series details: plot, cast (avatar chips), director, genres, rating, poster/backdrop, official YouTube trailers - Confidence-gated matching: provider tmdb_id trusted; otherwise normalized-title search with year gate (±1; series accept earlier premieres), season-suffix stripping, Cyrillic search-language override, and language-prefix fallback variants - Lazy season/episode enrichment: real episode names, overviews, stills - "Similar" rail (Xtream): TMDB recommendations matched to the catalog - Actor pages per portal with full filmography, availability filter and an Electron-only "All portals" scope backed by a batched DB_MATCH_TITLES worker op over the trigram FTS index Infrastructure: - SQLite cache table tmdb_metadata (details, search verdicts, seasons, persons; per-language, TTL-guarded), in-memory fallback for the PWA - Settings: enable toggle, own-API-key override with a live "check key" button; TMDB attribution in Settings and About - Embedded key stays an empty placeholder; CI injects TMDB_API_KEY via tools/tmdb/inject-tmdb-key.mjs when the secret is configured - normalizeTitle shared between renderer and DB worker - CSP: allow YouTube embeds (frame-src was 'none'; trailers never worked) Fixes and refactors along the way: - fix(stalker): Advanced Search sent bare get_ordered_list requests and skipped the auth handshake when isFullStalkerPortal was missing on the active-playlist meta — full portals answered "Authorization failed." and search looked empty; now mirrors the catalog request shape and routes through makeAuthenticatedRequest with URL-based detection - fix(stalker): TMDB fields survive info re-normalization; detail views prefer the store copy patched by async enrichment over stale snapshots - refactor(xtream): split oversized vod/serial detail components into component-scoped playback services; detail routes re-initialize on route param changes (router reuses them for detail-to-detail nav) - i18n: all new keys translated across the 18 locales Docs: docs/architecture/tmdb-metadata-enrichment.md + CLAUDE.md updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): provide route params observable to inline collection details, linearize regexes The global-collection inline detail host builds a fake ActivatedRoute for VodDetailsRouteComponent/SerialDetailsComponent with only snapshot.params. Since the detail components now read route.params via toSignal() (detail-> detail re-init), the missing observable crashed component construction and the content hero never rendered — broke dashboard-activation, favorites and recent Electron E2E on all platforms. Provide the params observable alongside the snapshot and assert it in the component spec. Also resolves both CodeQL js/polynomial-redos alerts: bracket-stripping in normalizeTitle now excludes opening delimiters inside the classes, and youtubeEmbedUrl extracts watch?v= ids with a linear two-pass match instead of "watch\?.*v=". Combining-diacritics range rewritten as explicit \u escapes (greptile note). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): surface TMDB-only VOD score in the rating badge, drop youtube.com from CSP Review follow-ups on PR #1123: the Xtream VOD detail badge renders rating_imdb, but the merge wrote the TMDB score only into `rating`, so a TMDB-only score was never displayed (Codex P2) — fill rating_imdb when the provider left it empty, mirroring the Stalker merge. All trailer iframes are normalized to youtube-nocookie.com, so the extra youtube.com frame-src allowance was dead surface (greptile) — removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): resolve confirmed review findings — matching correctness, race guards, cache schema Fixes the confirmed findings from the PR #1123 code review: - Stalker search: setSelectedContentType now runs BEFORE setSelectedItem, so the TMDB enrichment gate in the selection hook no longer sees the content type of the previously open tab (wrong/no enrichment after ITV -> search -> movie). - Title normalization is now two-tier (normalizeTitleKeys): the exact normalized form keeps a trailing year, the base form strips it and remembers the tag. Year stripping is anchored to the end of the title ("2001: A Space Odyssey" keeps its year) and language-prefix stripping is UPPERCASE-only ("It: Chapter Two" is no longer amputated). - All catalog matching (similar rail, actor pages, DB worker DB_MATCH_TITLES) compares exact forms first and only accepts year-stripped matches when the stripped tag is year-compatible (+-1) with the TMDB year — "Blade Runner" (1982) can no longer claim a catalog "Blade Runner 2049". CatalogTitleMatch carries the stripped trailingYear so the renderer can apply the guard to worker matches. - mergedBackdrops tolerates a plain-string backdrop_path; enrichment merge+patch blocks are wrapped in try/catch so a malformed provider payload can no longer become an unhandled rejection. - loadGlobalMatches (both actor routes) guards against actor->actor navigation races — a slow match for the previous person no longer overwrites the current one's results. - tmdb_metadata media_type CHECK widened to ('movie','tv','person') and person rows now use the honest 'person' type (TmdbCacheMediaType). Pre-release dev DBs with the narrow CHECK are rebuilt in place — the table is a pure cache, so the migration is a self-healing drop-and-recreate keyed off sqlite_master. Docs updated (tmdb-metadata-enrichment.md, CLAUDE.md). New regression coverage: title-normalization.util.spec.ts, two-tier cases in tmdb-similar.util.spec.ts and title-match.operations.spec.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8eb0fe3261 |
feat(playback): embedded mpv pre-0.22 hardening, Linux parity, and localization (#1122)
* fix(playback): harden embedded mpv session handling and support detection - guard the session controller against late startup rejections clobbering a newer session during fast channel zapping - exclude the refresh timestamp from the session-update dedup key so idle sessions stop re-emitting IPC updates every 500 ms - macOS: reconcile async loadfile replies by request id so a rejected seek/aid/speed on a live stream no longer flips the session to error - append --ozone-platform=x11 on Linux in main.ts so direct binary and AppImage launches match the packaged .desktop launcher behavior - return a sandbox-specific unsupported reason in Flatpak/Snap instead of asking the user to install mpv inside the sandbox - update the stale "macOS only" embedded MPV claim in CLAUDE.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(playback): populate Linux audio tracks and fix ARM Linux packaging - Linux poller now reads track-list/count each tick and walks the scalar track-list/N/* sub-properties when the count changes, so the audio-track menu is no longer empty; selection reconciles from the aid property - afterPack replaces the x64 embedded_mpv.node with an embedded-mpv-unavailable.txt marker in arm64/armv7l Linux packages, and package-layout verification rejects foreign-architecture addons while requiring the marker - extend native source invariants for the non-fatal async-reply rule (macOS) and the Linux track-list polling contract - document the Linux track-list mechanics and ARM packaging behavior in docs/architecture/embedded-mpv-native.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(playback): embedded mpv player UX polish and full localization - click on the video toggles pause (same action as Space) with a 250 ms grace period so double-click fullscreen cancels the pending pause; no DOM overlay is drawn — the dock transport icon is the feedback - timeline scrubbing previews the drag position locally and commits a single seek on release instead of one IPC seek per drag pixel - translate all player UI strings (controls, tooltips, aria-labels, status and recording messages) via new EMBEDDED_MPV.PLAYER.* keys, synced across en + 17 locales through the i18n-fill workflow - replace the legacy @Output() EventEmitter with the signal output() API Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): react to language changes and respect ozone platform hint Address review feedback on #1122: - add a translationsTick signal (onLangChange/onTranslationChange/ onDefaultLangChange) read by every computed() and template helper that calls translate.instant(), so labels re-evaluate on a runtime language switch and when the translation file finishes loading after mount - suppress the Linux --ozone-platform=x11 fallback when the user set ELECTRON_OZONE_PLATFORM_HINT, matching the existing respect for an explicit --ozone-platform switch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3659de796c |
chore(docs): remove external wiki-export tooling and instructions (#1121)
The wiki export to an external Obsidian vault is unused. Remove the wiki:export/test:wiki-export npm scripts, the external-wiki-sync architecture doc, and the IPTVNATOR_WIKI_VAULT workflow instructions from CLAUDE.md and AGENTS.md. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1c710d69ef |
chore(cleanup): delete nine dead components, orphaned i18n keys and unused deps (#1116)
* chore(cleanup): delete nine dead components, orphaned i18n keys and unused deps Removes verified-dead components (0 class/selector references outside their own files): EpgListComponent (+ epg-list-item), EpgViewComponent, LiveEpgPanelComponent, StalkerCollectionChannelsListComponent, NavigationComponent, FilterSortMenuComponent, video-player ToolbarComponent, PortalCollectionShellComponent and LoadingOverlayComponent, together with their barrel exports. Alive code extracted from the deleted trees: - LiveEpgPanelSummary -> libs/ui/shared-portals/src/lib/live-epg-panel-summary.ts - EpgProgramActivationEvent -> libs/ui/epg/src/lib/epg-program-activation-event.ts - epg-list.utils.ts trimmed to the three timeline-used helpers and moved to libs/ui/epg/src/lib/epg-program.utils.ts - epg-item-description/ moved up out of the deleted epg-list/ folder Also removes 18 i18n keys now unused (from all 18 locales), dead CSS selectors targeting the deleted elements, and unused dependencies: lodash (+ @types/lodash), semver, @ngrx/component-store and @videojs/http-streaming (videojs-quality-selector-hls declares no peer dependency on it; video.js 8 bundles VHS). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(coverage): move shared-portals to Tier C, fix stale doc references The Tier A gate failed in CI because deleting the dead epg-view and live-epg-panel components removed the only specs in libs/ui/shared-portals. The lib now contains a single type-only interface (LiveEpgPanelSummary), so there is no runtime code to unit test; reclassify it to Tier C with a documented reason, matching the gate's own guidance. Also update remaining doc references to the deleted components in docs/architecture/stalker-epg.md, iptvnator-ui-guidelines.md and CLAUDE.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e14b8ae8d9 |
feat(ci): enforce lint, guard coverage policy, add max-lines rule (#1117)
* fix(lint): resolve module-boundary and prefer-inject errors Retag workspace-shell-util as type:data-access to match its injectable services that depend on @iptvnator/services, and convert RemoteControlService to inject(HttpClient). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(lint): enforce max-lines 400 with generated baseline Add a max-lines ESLint error (hard cap 400 raw lines per TypeScript file) per the repo file-size rule. The 134 pre-existing offenders are baselined in tools/eslint/max-lines-baseline.mjs, regenerable via generate-max-lines-baseline.mjs; the list should only shrink. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ci): enforce lint on PRs and guard coverage policy drift - Add a Lint job to ci.yml running nx run-many -t lint --all, so module-boundary tags, legacy-alias bans, and max-lines gate merges. - Fix the root lint script (was linting only electron-backend). - Add tools/coverage/check-coverage-policy.mjs: fails CI when a project with a test target is missing from coverage-policy.json; wired into coverage:ci as coverage:policy:check. - Run Tier B/C unit tests in CI without coverage (list derived from the policy), so website/packaging/remote-control tests run on PRs. - Replace the hand-picked 16-project test:unit:ci list with --all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: document CI lint enforcement and coverage policy guard Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): address bot review feedback on policy guard and baseline generator - Drive Tier B/C validation from each policy entry's validationCommand (falling back to nx test), skipping projects with an e2e target since the E2E workflow already runs them (Codex). - Fail when a Tier A entry has no test target (Greptile, adapted: checking all entries against test targets would false-positive on the intentionally spec-less e2e/mock-server tiers). - Guard against missing JSON array in nx show projects output (Greptile). - Scan .tsx files in the max-lines baseline generator to match the ESLint rule's file patterns (Greptile). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0b526b119a |
feat(epg): add vertical list view for the live EPG panel (#1115)
* feat(epg): add vertical list view for the live EPG panel
Add an EPG list view — a vertical, single-day programme list — as an
alternative rendering of the live EPG panel, selectable via a new
Settings → EPG → "Guide view" toggle (epgViewMode: 'timeline' | 'list',
default 'timeline' so existing users see no change).
- New EpgListViewComponent (app-epg-list-view) mirrors
EpgTimelineComponent's input/output contract 1:1, so all four live
hosts (M3U player, unified live tab, Xtream, Stalker) swap the panel
with a plain @if and identical bindings.
- Reuses the shared view-agnostic EPG modules (classifyTimelineWhen,
hasProgramsForDateKey, epg-archive.util catch-up gating,
epg-summary.util collapsed-summary maths, epg-date helpers,
EpgProgrammeDialogService, app-epg-timeline-empty-state) — no
duplicated logic.
- Rows show time range, title, optional description, live progress on
the on-air row, catch-up "Watch" on past rows when archive playback
is available, and a details dialog; keyboard activation guards
nested buttons (target === currentTarget).
- Auto-focuses the on-air row on channel select, restores it across
collapse/expand remounts, and shows a sticky in-flow "On now" strip
(never overlaying rows) when the current programme is scrolled away;
all scroll maths is rect-based relative to the scroller.
- List mode raises only the inline panel height via an epg--list
modifier (--epg-inline-height clamp); timeline and collapsed heights
are unchanged.
- Setting flows end-to-end (Settings interface → DEFAULT_SETTINGS →
SettingsStore/StorageMap → segmented control in the EPG section);
Electron-only UI, PWA stays on the timeline default. i18n keys added
to all 18 locales.
- Tests: new component/row/utils/scroll-controller specs, settings
persistence spec, swap tests in all four host specs, and Electron
E2E for the settings round-trip and the rendered list view. Docs
updated (m3u-playlist-module.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(epg): address list-view review findings from Codex and Greptile
- Reset the list view to today when a new channel's programme set
arrives while the user is parked on another day (timeline parity):
the scroll controller now keys by the full programme-set identity
(programsFocusKey) and commits today before focusing, instead of
silently stranding the new channel on the stale day. (Codex P2)
- Centralise the 'timeline' fallback as a resolvedEpgViewMode computed
on SettingsStore; the four live hosts consume the derived signal
instead of duplicating the `?? 'timeline'` expression. (Greptile P2)
- Extract the component's reactive plumbing into
registerEpgListViewEffects(), bringing the component back under the
300-line guideline (290). (Greptile P2)
- Controller spec rewritten around programme-set fixtures with new
coverage: return-to-today on channel switch, day navigation left
alone, no-takeover when today has no data, empty-set no-op.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(epg): drop malformed programmes from the list-view day filter
Reject programmes whose stop is not after their start in
buildEpgListRows — same as the timeline's buildTimelineBlocks. Bad
provider data would otherwise render impossible time ranges and could
even be offered as catch-up playable. (Codex P2 on
|
||
|
|
401012eac4 |
feat(epg): rework live EPG panel into a horizontal timeline (#1102)
Replaces the vertical EPG list with a shared horizontal `app-epg-timeline` ribbon across all live surfaces (M3U player, unified live tab, Xtream, Stalker): zoom, day navigation, short-programme grouping, catch-up/timeshift, and per-state empty views. Backend gains timezone-aware `datetime()` comparisons, unscoped source fallback, non-ASCII candidate matching, and chunked candidate queries. Timeline split into reusable, view-agnostic modules (archive/summary/dialog service/render util/scroll controller) for the future EPG list view. Fixes landed during review: - honor the controlled `selectedDate` input (seed via linkedSignal) - restore ribbon position across collapse/expand - keep the ribbon mounted when scrolling across a gap day - don't trigger block playback on Enter from nested watch/info buttons - don't reset timeshift playback on the 30s now-tick during EPG gaps Greptile 5/5 (safe to merge); Codex clean; CI green. |
||
|
|
b1119189e2 |
feat(updater): add GitHub releases desktop updater
Adds the GitHub Releases desktop updater with release notes, startup notification, packaging metadata, tests, and CI fixes for Electron E2E. |
||
|
|
148affdfca |
Merge pull request #1093 from larsemig/feat/embedded-mpv-command-palette
feat(workspace): add embedded MPV to the command palette |
||
|
|
d7e7a8abda | fix(workspace): defer embedded mpv palette support check | ||
|
|
007ae7028d | fix(xtream): key catchup probes by output formats | ||
|
|
ca81bc552f | fix(electron): pin private probe redirect hosts | ||
|
|
16a93bcaaf | fix(electron): block private xtream probe redirects | ||
|
|
1679373bb2 | feat(xtream): auto-select stream output format | ||
|
|
12f3f1fc05 | fix(xtream): resolve catchup timeshift variants | ||
|
|
f7ad17298f |
feat(workspace): add embedded MPV to the command palette
Register an "Switch player to Embedded MPV" command in the Cmd+K command
palette so the embedded MPV player can be activated like the other players.
Visibility is gated on an async getEmbeddedMpvSupport() check, mirroring the
Settings dropdown so the command only appears when embedded MPV is usable.
Generalizes the per-command visibility flag from desktopOnly to a `requires`
discriminator ('none' | 'managed-external' | 'embedded-mpv').
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
6373c798a1 |
fix(stalker): load full playlist for authenticated routes
Fix Stalker route initialization so full portal sessions load auth-capable playlist data before category/content requests. Add regression coverage for full playlist lookup, empty lookup fallback, fast-path explicit portal metadata, and authenticated request routing. |
||
|
|
e801028005 |
feat(epg): support playlist-scoped sources
Add playlist-scoped EPG source support for M3U playlists. |
||
|
|
3d4550f6c5 |
Merge remote-tracking branch 'origin/master' into agent/dashboard-rail-performance
# Conflicts: # apps/electron-backend/src/app/database/operations/content.operations.spec.ts # libs/shared/interfaces/src/lib/electron-api.interface.ts |
||
|
|
89916af9d8 |
feat(search): add workspace global search with M3U support
Moves global search into a routed workspace view, adds M3U live/radio results, lazy pagination, and DB-backed matching improvements. |
||
|
|
b6c5367d63 | fix(dashboard): speed up startup rails | ||
|
|
2edc430935 | feat(xtream): add live all-items grid | ||
|
|
6ad5f5e207 | fix(xtream): address live recents review feedback | ||
|
|
210f0ebabc | feat(xtream): show recently added live channels | ||
|
|
3d2549dace |
Merge pull request #1067 from 4gray/ci/windows-embedded-mpv
ci(electron): require embedded MPV in Windows artifacts |
||
|
|
1073ce5350 | ci(electron): require embedded mpv in windows artifacts | ||
|
|
ce78669c17 | fix(electron): disable service worker in desktop runtime | ||
|
|
20d0f01428 | fix(xtream): address portal review feedback | ||
|
|
254879f92b | fix(xtream): improve portal compatibility | ||
|
|
bc4a5958dc |
fix(remote-control): apply volume to built-in M3U players
Closes #1061 |
||
|
|
4094a36fef | Harden Linux embedded MPV packaging (#1043) | ||
|
|
8d672f8b81 | feat(playback): add web player series navigation (#1049) |