- windows_d3d11.rs / windows_player_surface.rs: detect whether the
active display supports HDR and, when the "HDR output" preference is
on, create the D3D11 swap chain with an HDR-capable format; expose
hdr_display_supported() for the frontend to query.
- mpv_render.rs: drop the now-redundant format/width/height fields
from the D3D11 render target — mpv_d3d11_fbo only needs the texture,
which already carries its own format/dimensions.
- linux_player_surface.rs: track whether the current Vulkan context is
HDR-capable on a shared flag for parity with the Windows path.
- storage.rs: add read_pref_bool() to read boolean prefs (used for the
hdrEnabled toggle).
- lib.rs: register the player_hdr_supported command; tauri.conf.json
sets a transparent window background so HDR/SDR compositing doesn't
show a black flash.
- Frontend: add an "HDR output" toggle in Playback settings, auto-detect
and store display support on first run via player_hdr_supported.
- mpv_render.rs: enable "info"-level mpv logging and route messages to
the matching Rust log level with prefix/level context; use mpv's
native int64/flag property getters (get_i64_property/
get_flag_property) instead of parsing track-list strings, and log
each queried track (id/title/lang/codec/selected/external) to make
subtitle-track issues diagnosable from the app log.
- player.rs: wire up the previously-unused torrent_sibling_subtitles
scan as a new player_torrent_sibling_subtitles command — finds
subtitle files (.srt/.ass/.ssa/.vtt/.sub) bundled in the same torrent
as the playing video, matching by exact filename, by a shared
S01E02-style episode tag, or (for single-video torrents) by being
the only video in the release.
- lib.rs: register the new command, elevate "subtitles:" debug_log
lines to warn so they're visible without FLUXA_DEBUG_LOGS, and make
torrent_engine_request pub(crate) so player.rs can call it.
- usePlayer.ts: load resolved subtitles into mpv one at a time (was
all at once), which was triggering 429s from subtitle CDNs; merge in
the torrent-bundled subtitles found above.
- mpvPlayer.ts: add the player_torrent_sibling_subtitles wrapper.
- subtitles.ts: trace addon subtitle resolution via debug_log, and cap
addon results to 2 per language (OpenSubtitles returns dozens of
near-duplicates).
Downstream of the fluxa-core rquickjs "bindgen" feature removal —
prunes bindgen/cexpr/clang-sys and their transitive deps from the lock
file (avoids requiring libclang to build).
- 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 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
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.
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.
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.
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.
AppImage now keeps libgst*/liborc* libs instead of stripping all .so files, and deb/rpm packages declare gstreamer1.0-plugins-base/good as dependencies.
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.
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.
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.
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.
Applying the resume position via loadfile's start= option seeked
before mpv had indexed the stream's keyframes, landing the video
decoder in a bad spot while audio (no keyframe dependency) kept
advancing. Load at 0 and issue a real runtime seek once playback
actually restarts instead, same as a manual seekbar click. Also
expose a "resuming" status flag so the loading overlay stays up
through the whole seek instead of dismissing at the wrong restart
event and exposing the mid-seek frame directly to the user.
Restore the //!WHEN OUTPUT > 1.2x gates in Upscale_CNN_x2_S/M/L: with
full restore/upscale/thin chains the native-resolution effect comes
from Restore/Thin, and the gates let the upscale stages self-disable
at 1:1 instead of force-upscaling and downscaling back.
Join chain paths with ';' on Windows (mpv's path list separator there;
':' would also split drive letters). Replace cscale=ewa_lanczossoft
with ewa_lanczos — gpu-next has no libplacebo analog for lanczossoft
and was silently falling back.
Verified all three chains against the bundled libmpv: no shader or
scaler mapping errors; visible pixel diffs at 2x upscale and, via
Restore/Thin only, at 1:1 native resolution.