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.
* 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>
* 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>
Adds experimental embedded MPV support for Windows and Linux, expands packaging/runtime validation, and updates CI coverage for the cross-platform native addon.
- Introduced tooling for building and staging the macOS `libmpv` runtime for IPTVnator's embedded MPV player.
- Added `build-macos-runtime.mjs` for building an LGPL-compatible runtime from source.
- Created `stage-macos-runtime.mjs` for staging the built runtime artifacts.
- Implemented validation for the packaged embedded MPV runtime in `electron-after-pack.cjs` and `embedded-mpv-macos.cjs`.
- Updated packaging scripts to ensure the embedded MPV runtime is correctly integrated and validated during the build process.
- Added README files to document the expected layout and usage for the embedded MPV runtime artifacts.
Entire-Checkpoint: c6e522b4276c
Update GitHub Actions workflow triggers to use the master branch
for push and pull_request events. Replace the previous nx branch
restriction so CI (build, make and e2e tests) runs on master and tag
events as intended.
This enables workflows to execute for changes merged to master and
aligns branch configuration across workflows.
Add a publish-snap job to the CI workflow that runs after build and
only triggers for version tags (refs/tags/v*). The job downloads the
linux build artifacts, installs snapcraft, locates the generated .snap
file and uploads it to the Snapcraft Store releasing to the stable
channel. It validates the presence of a snap file and fails if none is
found.
This enables automated Snap distribution for tagged releases using the
SNAPCRAFT_STORE_CREDENTIALS secret.
Update GitHub Actions workflow to support multiple OS runners and CPU
architectures, standardize matrix keys, and adjust platform-specific
steps and artifact names.
- Replace simple os matrix with include entries specifying os, runner,
and arch to allow macOS x64/arm64 separation and explicit runners for
linux/windows.
- Use runner matrix value for runs-on and change timeout/strategy
accordingly.
- Adjust conditional checks to use normalized os values (linux, macos,
windows) rather than platform-specific runner labels.
- Install Linux system deps only when os == 'linux' and keep Flatpak
setup for electron-builder.
- Add a macOS-specific make step that passes the architecture to the
electron make command, while keeping a combined make step for
non-macOS builds.
- Upload macOS artifacts per architecture and rename artifact paths to
include arch; update release files list to include both macOS x64 and
arm64 artifact patterns.
These changes enable reliable, architecture-aware macOS builds,
prevent native module conflicts across runners, and ensure artifacts
are labeled and uploaded correctly.
Remove unused url.format import and switch from loadURL with a
formatted file: URL to loadFile for production. This simplifies code,
avoids manual file URL construction, and makes the production path
loading more robust.
Also update CI job to build the frontend via the NX command
(npx nx build web --skip-nx-cache) instead of npm run build:frontend,
aligning the workflow with the monorepo's canonical build command.
Update the GitHub release action to mark releases from pull
request events as prerelease and to set a safe tag name when the
is not triggered by a tag ref.
- Set prerelease to true when github.event_name == 'pull_request'
so draft releases created during PR workflows are marked as
prereleases.
- Use the tag name from github.ref_name when the workflow is
triggered by a tag; otherwise generate a deterministic test tag
"test-<sha>" to avoid using an invalid or empty tag name.
This prevents accidental full releases from PR runs and ensures the
action always has a valid tag_name value.