Commit graph

441 commits

Author SHA1 Message Date
KhooLy
87f209d7fb use shared core business policies 2026-07-18 21:47:47 +03:00
KhooLy
dcbd7880e6 use native fullscreen on Linux 2026-07-18 21:09:47 +03:00
KhooLy
e054fd06bc delegate reusable policy to fluxa core 2026-07-18 21:05:30 +03:00
KhooLy
75c1b3a9d4 delegate library and playback policy to core 2026-07-18 20:18:06 +03:00
KhooLy
d3a001d193 Move Linux Vulkan rendering off the GTK main thread
The Vulkan render+present ran inside the 16ms glib timeout on the GTK
main thread — the same thread WebKit uses for overlay input — and FIFO
presents plus vkAcquireNextImageKHR (100ms timeout) blocked it for a
large slice of every frame, which made the React player overlay laggy
compared to the OpenGL path (which just draws into GTK's own FBO).
Windows and macOS already render on a dedicated thread; Linux now does
too. The GTK loop only publishes the surface size through atomics and
keeps the GDK/Wayland surface handles.

Also:
- prefer VK_PRESENT_MODE_MAILBOX_KHR when available so presents don't
  block on vsync at all
- take the player_renderer lock only around the mpv render itself, not
  across acquire/present, so overlay IPC (seek, status polls) no longer
  queues behind vsync waits
- on X11, create the Vulkan surface on a private XOpenDisplay connection;
  GTK never calls XInitThreads, so presenting over GTK's Display from
  another thread would race
- a load issued before the render thread has wired mpv's Vulkan render
  context is now deferred and retried instead of failing
2026-07-16 19:12:36 +03:00
KhooLy
a31ae053de Move business policy from the shell into fluxa-core
- 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
2026-07-16 18:53:51 +03:00
KhooLy
c1f36a7359 Route UpdateModal strings through i18n 2026-07-16 18:05:58 +03:00
KhooLy
a76674ee48 Recover swapchain state after a Vulkan render error
Bailing out between vkAcquireNextImageKHR and present left the acquire
semaphore with a pending signal and the image unpresented; the next frame
reused the semaphore in that state. On any render_and_present failure,
wait for the device to idle, recreate the binary semaphores, and rebuild
the swapchain.
2026-07-16 18:05:58 +03:00
KhooLy
cb059a6fcb Pin guarded fetches to the addresses vetted by net_guard
ensure_public_host checked DNS and then let reqwest re-resolve, so a
rebinding DNS entry could swap in a private IP between the check and the
request. vetted_client resolves once, validates every address, and pins
the connection to that set via resolve_to_addrs. Redirects to IP-literal
private hosts are refused as well. The shared artwork client goes away
since pinning is per-host.
2026-07-16 18:05:58 +03:00
KhooLy
4411d78580 Create storage key file with 0600 from the start
fs::write followed by set_permissions left a window where the key was
world-readable.
2026-07-16 18:05:43 +03:00
KhooLy
3f8368dbae Generate cast proxy tokens from the OS CSPRNG
RandomState hashes are seeded per-process but aren't a CSPRNG; the proxy
listens on all interfaces so the token is the only access control.
2026-07-16 18:05:43 +03:00
KhooLy
b04b8b830f Reject nuvio_request paths that don't start with /
A path like @evil.com turned the Supabase host into userinfo and sent the
anon key to an attacker-chosen host.
2026-07-16 18:05:43 +03:00
KhooLy
5a07658981 Interrupt runaway plugins and cap QuickJS memory
The 60s tokio timeout only fires at await points, so a plugin stuck in a
synchronous loop blocked its spawn_blocking thread forever. An interrupt
handler with the same deadline preempts synchronous execution too, and a
256MB memory limit stops unbounded allocation.
2026-07-16 18:05:43 +03:00
KhooLy
c08a9c610e Add missing i18n keys for render backend and player engine settings
Some checks failed
Warm Windows ANGLE Cache / warm-angle-cache (push) Has been cancelled
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.
2026-07-15 19:38:34 +03:00
KhooLy
b2ad5e55ea Surface Nuvio sync errors in the settings UI
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.
2026-07-15 19:36:14 +03:00
KhooLy
736b99313e Add stream/magnet link copy and download-from-context-menu
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.
2026-07-15 19:36:03 +03:00
KhooLy
dfc1299215 Add libVLC as an alternate playback engine
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.
2026-07-15 19:35:24 +03:00
KhooLy
21c674dc43 Add Vulkan and D3D11 GPU render backends for mpv playback
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.
2026-07-15 19:34:12 +03:00
KhooLy
1022d26dda Fix silent audio after switching audio track mid-playback
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.
2026-07-14 22:25:11 +03:00
KhooLy
82560d788b Bundle GStreamer plugins in Linux builds for media framework support
AppImage now keeps libgst*/liborc* libs instead of stripping all .so files, and deb/rpm packages declare gstreamer1.0-plugins-base/good as dependencies.
2026-07-13 19:58:44 +03:00
KhooLy
6887d8c0b2 Add RSA/ECDSA sign/verify for full crypto.subtle parity with Nuvio
Closes the gap flagged in the previous commit. Adds RSASSA-PKCS1-v1_5
(SHA256/384/512, via the rsa crate) and ECDSA P-256 (via p256/ecdsa)
sign/verify, native-backed through __crypto_sign_hex/__crypto_verify_hex,
with crypto.subtle.importKey/exportKey now accepting pkcs8/spki key
formats. RSA-SHA1 signing is not supported (rsa crate doesn't re-export
sha1; SHA1 signatures are already deprecated).

Verified against independently-produced signatures rather than just
self-consistency: RSA against an OpenSSL-signed message, ECDSA against
a signature produced by Python's `cryptography` library (DER-to-raw
r||s converted, matching WebCrypto's signature format) — both cross
into the native Rust layer and through the real crypto.subtle JS path.
14 plugin_runtime tests + 6 crypto_bridge tests, all passing, zero
clippy warnings.
2026-07-13 18:02:18 +03:00
KhooLy
112dde688e Add crypto polyfill to the plugin runtime (CryptoJS + WebCrypto)
Adds the crypto surface Nuvio scrapers commonly need to decrypt
obfuscated stream URLs: native digest (MD5/SHA1/256/384/512), HMAC,
PBKDF2, and AES-CBC/ECB/GCM (via sha1/sha2/md-5/hmac/pbkdf2/aes/cbc/
ecb + the existing aes-gcm dependency), exposed through the same
__crypto_*_hex bridge shape Nuvio uses, plus the CryptoJS-compatible
and crypto.subtle JS shims ported from JsBindings.kt.

RSA/ECDSA sign/verify are deliberately not implemented — crypto.subtle
.sign/.verify throw clearly rather than silently failing.

Verified against independently-known-correct vectors, not just
"compiles": SHA-256("abc"), HMAC-SHA256 (cross-checked against
Python's hmac/hashlib), AES-CBC roundtrip, and crypto.subtle.digest
all pass through the real JS engine end to end.
2026-07-13 17:48:38 +03:00
KhooLy
78acb8e6fb Add the fetchPluginManifest effect handler
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.
2026-07-13 17:24:08 +03:00
KhooLy
af37a159d4 Wire plugin scrapers into detail stream loading
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.
2026-07-13 17:21:33 +03:00
KhooLy
18e0eed24c Add production plugin runtime: rquickjs sandbox behind run_plugin_scraper
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.
2026-07-13 17:17:41 +03:00
KhooLy
d4e170be27 Regenerate coreMethods.ts for pluginManifestParse/pluginStreamResultsParse
fluxa-core added these two core_invoke methods for parsing Nuvio-style
plugin manifests and getStreams() output.
2026-07-13 16:22:55 +03:00
KhooLy
c0b5ecb0b0 Add rquickjs plugin-execution spike (Nuvio-compatible scraper plugins)
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.
2026-07-13 16:14:22 +03:00
KhooLy
18db03bb1f Delegate similarTitles/search-merge/fetch-policy/link-classification to fluxa-core
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.
2026-07-13 04:29:58 +03:00
KhooLy
1bbea10434 Fix stale Continue Watching episode labels 2026-07-13 03:15:48 +03:00
KhooLy
2c5b159605 fix(player): wait for rendered video before handoff 2026-07-13 02:24:24 +03:00
KhooLy
91a68c899e Fix YouTube trailer playback and fullscreen overlay rendering
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.
2026-07-13 02:00:14 +03:00
KhooLy
fde3f44f21 fix(player): apply stream request headers 2026-07-12 18:58:48 +03:00
KhooLy
ebf8a2adf6 Preserve Nuvio collection sources in desktop sync 2026-07-12 18:49:06 +03:00
KhooLy
54371cb489 Add Discord community link to README 2026-07-12 14:09:44 +03:00
KhooLy
391c7b9893 Pair adaptive video/audio streams for YouTube trailers without HLS
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.
2026-07-12 13:51:38 +03:00
KhooLy
73cde13c90 Add TMDB as a built-in metadata provider
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.
2026-07-12 13:49:28 +03:00
KhooLy
43aa2b863b Stop dropping disabled addons during Nuvio profile import
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.
2026-07-12 13:14:49 +03:00
KhooLy
62e8af09df Filter disabled addons from Home catalogs and hero banner
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.
2026-07-12 13:14:44 +03:00
KhooLy
5042300b9f Select high quality trailer variants 2026-07-12 12:59:08 +03:00
KhooLy
be598c90d5 Move trailer resolution out of streaming engine 2026-07-12 12:52:36 +03:00
KhooLy
8c8d937ca5 chore: bump version to 0.1.27 2026-07-12 01:45:27 +03:00
KhooLy
c181126a07 Hide Discover result count 2026-07-12 01:43:35 +03:00
KhooLy
885a341a4f Use loading error screen for playback failures 2026-07-12 01:29:37 +03:00
KhooLy
099e2925b6 Fix catalog pagination deduplication 2026-07-12 01:24:03 +03:00
KhooLy
84349c1daa Replace blocking playback-error modal with a toast, surface subtitle addon failures
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.
2026-07-12 01:18:14 +03:00
KhooLy
c88e6c9d1b Allow removing a single recent search entry
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.
2026-07-12 00:57:57 +03:00
KhooLy
aaf1ddfe18 Remove torrent source debug panel from loading overlay
The Source/Provider/File/File index/Info hash/URLs block is debug
info with no value to a regular user watching something load.
2026-07-12 00:57:57 +03:00
KhooLy
d24f8317fc Style poster titles bold/white with a gray release year
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.
2026-07-12 00:55:16 +03:00
KhooLy
240aadf6a0 Load more discover results as the user scrolls
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.
2026-07-12 00:55:06 +03:00
KhooLy
202dcfa923 Exclude search-only catalogs from discover type filter
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".
2026-07-12 00:54:30 +03:00