- libvlc_render.rs: locate/load libvlc more robustly (Windows DLL
search path, plugin path discovery), route libvlc log messages into
the app logger, lazily create the media player, and add audio
mute/track and subtitle-track control bindings.
- macos_player_surface.rs: attach the libVLC output to the native
NSView so it renders inside the app window (macOS), switching
between the Vulkan and libVLC render paths based on the active
engine.
- Update the player-engine description strings to reflect that libVLC
now renders embedded rather than in its own window.
The external-stream check ran after showPlayerLoading had already
fired, so tapping a paywall/config-link stream card briefly opened the
player loading overlay before falling back to the browser. Moved the
check to the top of handlePlay, before any player UI state is touched.
The TS wrapper never sent it, so every addon manifest fetch failed at
the FFI boundary with "missing field unknownName" before the manifest
body was even read.
Replaces the local regex-based detection heuristic and search-result
matching with calls into fluxa-core, so the same logic Android can now
also use isn't duplicated (or drifting) between platforms.
Continues the shared-core migration: addon sort/priority ordering and
disabled-addon filtering now route through fluxa-core instead of being
computed locally, alongside the broader policy delegation already
in flight across screens and hooks.
- nuvioSync.ts drops its in-TS import/merge policy (watchlist mapping,
progress construction, watched reconciliation, savedAt bumps, profile
and collection mapping) and now orchestrates pulls around
nuvio* core methods, including the pending savedAt-bump behavior
- animeDetection.ts deleted: dead duplicate of core anime_detection,
playback already used coreDetectAnimePlayback
- air-date refresh due-check, SIMKL scrobble stop threshold, and the
torrent readiness budget now come from core instead of local constants
settings.render_backend(_desc) and settings.player_engine(_desc) were
referenced via t() but never added to either locale file, so the
hardcoded || fallback strings were silently used instead.
Previously sync failures were silently swallowed. Record errors into
NuvioSyncMeta on both the automatic and manual sync paths, and show
a "Sync error" status line in the sync service popover when present.
Adds a context menu on the player overlay and source rows to copy a
stream's direct/magnet link or trigger an offline download, backed
by a new stream_magnet_link command and a torrent-download resolver
that reuses (or starts) the torrent server on demand. Also refactors
start_torrent_stream's health-check into ensure_healthy_torrent_base_url
for reuse, and keeps the torrent server alive across stream stops so
downloads can still be resolved, tearing it down only on app exit.
Introduces a PlaybackEngine trait so playback can be routed through
either mpv or a new dlopen-based libVLC backend, selectable from
settings. libVLC currently plays in its own separate window rather
than embedded, since it doesn't support headless frame rendering.
Adds selectable Vulkan (Linux/macOS/Windows) and D3D11 (Windows,
HDR-capable) rendering paths alongside the existing OpenGL mpv
surface, wired through mpv's native render API, plus a Wayland
subsurface helper for Linux compositing and a settings toggle to
pick the backend.
Switching aid on a track mpv wasn't previously demuxing can leave its
packet queue stale at the current position until something forces a
resync, so audio stayed muted until the user manually seeked. Issue a
small forward exact seek right after the track switch to trigger that
resync automatically.
pluginRepositoryAddRequested dispatches a fetchPluginManifest effect
from fluxa-core, but nothing fulfilled it — it would have sat pending
forever. Adds the runner: fetch the manifest over http_fetch_text,
validate/normalize via pluginManifestParse, hand {manifestUrl,
manifest} back to completeEffect, which plugins::complete on the core
side upserts into state.plugins.repositories/scrapers.
Also adds executePlugin/fetchPluginManifest to the hand-maintained
EffectType union in contracts.ts, which must mirror fluxa-core's
EffectKind — the same kind of drift that caused the from_str bug
earlier in this branch.
fetchDetailStreams now also runs enabled plugin scrapers (read from
the headless engine's plugins.scrapers snapshot) alongside installed
addons: resolves a TMDB id via the existing resolveTmdbId lookup,
downloads and caches each scraper's JS over http_fetch_text (same
net_guard SSRF check as addon requests), runs it through
run_plugin_scraper, normalizes via pluginStreamResultsToStreams, tags
results with addonName = scraper name, and folds them into the same
streams/availableAddons list addon resources already produce — so
plugin-sourced streams flow through the existing merge/ranking path
with no new UI-side handling needed.
Scraper execution failures are swallowed (Promise.allSettled) so a
broken plugin can't take down addon stream loading.
Promotes the rquickjs spike into a real Tauri command. Executes a
Nuvio-compatible scraper's getStreams() in a sandboxed QuickJS VM with
fetch (routed through net_guard::ensure_public_host, same SSRF guard
as http_fetch_text/http_execute_text) and the full cheerio bridge.
Runs via spawn_blocking with its own current-thread tokio runtime
since the DOM state (scraper/tendril) isn't Send; a 60s wall-clock
timeout matches Nuvio's own plugin timeout.
DomBridge switched from Arc<Mutex<_>> to Rc<RefCell<_>> per clippy —
it's only ever touched from the single thread each execution owns.
fluxa-desktop is meant to be a thin shell over fluxa-core, but several
pieces of business logic had crept into TypeScript: Trakt/Simkl similar-
titles response mapping, search/discover source merging, addon fetch
retry/timeout/concurrency/race policy, AnimeSkip episode matching,
continue-watching week partitioning, library merge diffing, and
cast/director link classification (duplicated across three components).
All of it now calls the corresponding new fluxa-core methods and just
executes/renders the result.
Route trailer resolution through fluxa-core's engine instead of a
duplicated desktop-side implementation, and add a thin local HTTP
proxy so the WebView's <video>/<audio> elements can stream the
resolved googlevideo URLs (which reject requests without proper
Range/client handling) without hitting CORS or range-negotiation
issues directly.
Also fix fullscreen trailer playback: requestFullscreen() was being
called on the bare <video> element, so sibling overlays (subtitle
text, progress bar) weren't descendants of the fullscreened element
and disappeared. Both hero trailer views now wrap video + overlays in
a shared container and fullscreen that instead.
Users who remove Cinemeta/AIOMetadata and rely on a TMDB key alone got
nothing, because the TMDB key only powered narrow artwork/trailer
enrichment on top of a Meta object that had to already exist from a
real addon. When a TMDB key is set, it now acts as a synthetic addon
(transportUrl "tmdb://builtin") that plugs into the same catalog/meta
resolution pipeline real addons use for Home/Discover catalogs, search,
meta detail, and episode lists (including thumbnails and title logos
from TMDB's own /images endpoint) - everything except streams, same as
Cinemeta.
The synthetic addon is never persisted, never pushed to Nuvio/Stremio
sync, and never shown in the Addons list; each catalog/meta/episode
call site is patched individually to bypass the addon-protocol URL
builder (which would mangle the tmdb:// scheme) and resolve it
in-process instead via src/core/tmdbAddon.ts. A new "Prefer TMDB over
other metadata addons" toggle controls whether it's checked first or
only fills gaps.
fetchAddonManifests filtered the pulled addon list down to only
enabled entries before writing it to local storage. Since this import
runs automatically on startup and on every periodic Nuvio health
check, disabling an addon (which pushes enabled:false to Nuvio) got
that addon wiped from local storage on the next sync instead of just
marked inactive. Keep all pulled addons in the descriptor list and
only skip the manifest fetch for disabled ones.
Disabling an addon never invalidated Home's catalog/billboard state,
so its content kept appearing until an unrelated cache refresh. Filter
disabled addons before building metadata feeds, and trigger a forced
Home reload right when an addon is toggled.
Playback errors now show as a dismissible top-right toast instead of a
full-screen backdrop, using a new reusable Toast component. Subtitle addon
and track load failures, previously swallowed silently, are now collected
and surfaced as a warning toast too.
The header search bar's recent-history dropdown only supported
clearing the whole list. Add a per-item remove button, matching the
per-item removal already present on the full search screen.