Proves out running unmodified Nuvio Kotlin-plugin-format JS scrapers
(module.exports.getStreams) inside rquickjs, with native fetch and a
full cheerio bridge (load/select/find/text/attr/next/prev, chaining,
:contains()) ported from Nuvio's DomBridge.kt/JsBindings.kt.
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.
Falls back to the best matching avc1 video + audio/mp4 adaptive
formats (by resolution/bitrate) when no hlsManifestUrl is available,
returning audioUrl alongside streamUrl so the player can mux them.
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.
Titles were a muted, small label with no release info. Make the title
bold and white, and add the release year (or releaseInfo fallback) in
gray beneath it. Grew the reserved per-card height to fit the extra
line without clipping into the next row.
The catalog picker only ever fetched the first page of a catalog with
no way to see more. Fetch additional pages via the new
discoverPageRequested/fetchDiscoverPage effect when the grid scrolls
near the bottom, accumulating results per catalog+genre selection and
stopping once a page comes back empty.
Catalogs whose only extra is a required "search" field (e.g. an
"AI Search" catalog typed as "other") aren't browsable categories —
they were leaking into the type dropdown as their own tab. Also use
the plural "Movies" label to match "Series".
Adds an opt-in provider alongside AniSkip and IntroDB, gated by a
user-supplied anime-skip.com Client ID (settings mirror the existing
IntroDB API key flow). Matches shows via AniList ID, fetches episode
timestamps over the anime-skip.com GraphQL API, and merges results
with the other providers through the existing dedup path.
readCalendarMonth was truncating nextEpisodeAirDate before using it as
the item's dateIso, losing the time component; now the full string is
kept for dateIso and only truncated for the id key.
Torrent playback used to give up after a single 60s wait for ready
peers. Now it retries up to 2 more times (15s first attempt, then the
remaining budget split across retries), showing a "retry N/M" status
so a slow initial peer connect doesn't sink the whole playback attempt.
Also preloads the content logo mid-playback when it wasn't already
available, tightens the first-frame-ready check to not fire while
pausedForCache, and drops a redundant buffering-percent line from the
loading overlay.
Adds a settings toggle (with a configurable delay: 2/4/6/10s) to
autoplay the trailer over the hero background, with mute, progress,
fullscreen, and subtitle overlay controls. The subtitle cue parsing
(WebVTT/YouTube timedtext) and language selection previously private
to HeroSection.tsx move into a shared src/core/trailerSubtitles.ts so
the detail screen's hero can reuse the same logic.
No internet affects the whole app, not just Nuvio sync, and Nuvio
already has its own "servers unreachable" banner for real backend
outages. Split connectivity detection into a standalone
useOnlineStatus hook and OfflineBanner component, shown in place of
the Nuvio banner whenever the OS reports no network at all.
Wires detail.failedAddons through to both the movie and episode source
panels with a manual retry action, so a permanently failed addon isn't
just invisible — the user can see it happened and try again without
reopening the whole detail screen.
The health check treated any failed request as "Nuvio unreachable",
even when the user's own internet was down. Track navigator.onLine
via online/offline events and show a separate neutral banner instead
of blaming Nuvio when there's no internet at all.
Retries previously only fired when the network request itself threw
(timeout/DNS/connection failure); an addon responding with a 5xx/429
status or a truncated/malformed body counted as "got a response" and
was never retried. Now those cases retry as well (up to 3 attempts for
stream requests, with a shorter timeout on the retries).
Addon failures that survive every retry are no longer silently
indistinguishable from "this addon genuinely has no streams" — they're
tracked per addon and exposed as detail.failedAddons so the UI can
show a distinct notice instead of just omitting the addon.
loadStreams reused fetchDetailStreams' progressive-dispatch callback,
which sends detailStreamsAppended with no generation tag and bypasses
the staleness guard, merging into the same detail.streams/
availableAddons the Detail screen reads. loadStreams only ever
consumes the final aggregated array, so the progressive dispatch was
unused for it and just risked mixing another episode's addon results
into whatever the user is currently viewing.