Commit graph

94 commits

Author SHA1 Message Date
KhooLy
2594af72da Add torrent/magnet-link support to streaming and offline downloads
Derive a playable magnet URI from a stream's infoHash/sources (or
detect an existing magnet/torrent URL) via stream_magnet_link, and
let the offline-download planner accept torrent-sourced streams
instead of rejecting them outright.
2026-07-15 19:32:18 +03:00
KhooLy
0228bc97e4 Carry remote catalog sources through paging 2026-07-14 22:24:34 +03:00
KhooLy
5fb1c08afd Add Apple streaming engine bridge 2026-07-14 18:13:54 +03:00
KhooLy
3cc63912fd Make fetchDiscoverPage results append unconditional, not gated to the merge flow
The append-to-discover.results step only ran when initial_paging was
active (the internal multi-catalog merge sequence used on first load).
A manual, user-triggered discoverPageRequested dispatch (e.g. infinite
scroll load-more for the single currently-selected catalog) resets
initial_paging to empty in dispatch_discover_page, so its page results
landed only in discover.paging.items and were silently dropped from
discover.results. The recursive dispatch_next_initial_page call stays
gated on initial_paging so the merge-flow behavior is unchanged.
2026-07-13 23:08:51 +03:00
KhooLy
dff036ee63 Surface content_types on discover state for the client to build a dynamic Type filter
Kotlin's readDiscoverCatalogFilters effect now returns a contentTypes
list (distinct catalog types found across installed addons, e.g.
movie/series/anime), but the effect completion flows back through the
engine's own discover state before Kotlin ever sees it again. Added
content_types to DiscoverState and store it in the completion handler
for readDiscoverCatalogFilters, preserving it across a fresh discover
dispatch the same way catalogs/genres already are.
2026-07-13 22:48:58 +03:00
KhooLy
0b2acedd3c Add pluginStreamResultsToStreams: map plugin results onto the addon Stream shape
Lets the platform feed a scraper's getStreams() output straight into
the existing detailStreamsAppended merge path instead of needing a
parallel stream-ranking system for plugin-sourced streams.
Quality/size/provider/seeders/peers ride along in Stream's extra map
since they don't have first-class fields.
2026-07-13 16:56:50 +03:00
KhooLy
da11474e88 Wire plugin repository management into the headless engine
Adds a plugins domain to the headless engine (mirrors settings.rs/
offline.rs): pluginRepositoryAddRequested dispatches FetchPluginManifest,
completion upserts the repository and replaces its scrapers; remove and
per-scraper enable/disable are pure local edits. Reuses the existing
addon_store::normalize_plugin_repository_url helper for the URL.

Also fixes EffectKind::from_str missing the executePlugin/
fetchPluginManifest arms added in the previous commit (silently made
those effects undispatchable — completion always fell through as an
unrecognized type) and backfills the as_str/from_str roundtrip test's
variant list, which is supposed to catch exactly that.

ExecutePlugin (actually running scraper JS) is still a no-op in the
engine — that's the native QuickJS runtime work from the desktop-side
spike, not yet connected here.
2026-07-13 16:36:28 +03:00
KhooLy
e790a42dfa Add plugin manifest/stream-result types and parsing (Nuvio-compatible plugins)
Mirrors Nuvio's PluginManifest/PluginManifestScraper and the
parseJsonResults normalization from PluginRuntime.kt: validates
manifests (name/version/non-empty scrapers) and tolerantly parses a
plugin's raw getStreams() output (string-or-object url, blank
filtering) into typed results. Exposed via ffi.rs as
pluginManifestParse/pluginStreamResultsParse, and adds
ExecutePlugin/FetchPluginManifest to the effect vocabulary (not yet
wired to an engine completion flow — no-op for now).
2026-07-13 16:22:40 +03:00
KhooLy
a873bb6471 Guard refreshContinueWatching completion against stale generations
A late-resolving refresh from a superseded home load could clobber
continue_watching state written by a newer load, since only the
readHomeBootstrap arm checked the generation counter.
2026-07-13 05:03:24 +03:00
KhooLy
44d779c8b1 Move Trakt/Simkl mapping, search merge, and diff-plan logic from desktop into core
fluxa-desktop was reimplementing several pieces of business logic in
TypeScript instead of delegating to fluxa-core: Trakt/Simkl "similar
titles" response mapping, search/discover source merging, addon HTTP
retry/concurrency/race policy, AnimeSkip episode matching, continue-
watching week partitioning, library merge diffing, and cast/director
link classification. Adds the corresponding pure functions here and
exposes them over ffi so the shell just calls into core and executes.
2026-07-13 04:29:32 +03:00
KhooLy
ec06c7513a Add iOS build target for fluxa_core
Phase 0 of the fluxa-android KMP/CMP migration needs an xcframework +
Swift UniFFI bindings for fluxa_core. Adds an "ios" feature (full-api +
uniffi-bindings, no jni/dolby_vision, mirroring the existing "desktop"
feature), a staticlib crate-type for xcframework embedding, a
[bindings.swift] uniffi.toml section, and a macOS-only build script
that produces FluxaCore.xcframework for aarch64-apple-ios and
aarch64-apple-ios-sim.

Verified on Linux: `cargo check --target aarch64-apple-ios --features
ios` and the aarch64-apple-ios-sim equivalent both compile cleanly, so
the crate itself has no Android/JNI-only code leaking into the iOS
feature set. Actual linking/xcframework packaging needs a Mac with
Xcode — untested here.
2026-07-13 02:36:38 +03:00
KhooLy
b6523a7749 Add YouTube trailer resolution via ANDROID_VR client
Resolves trailer video/audio URLs through the engine's effect system
(watch-page config fetch, then player API), pairing the best AVC1
video with the best audio track. Uses the ANDROID_VR client instead
of plain ANDROID since ANDROID-issued CDN URLs are capped to serving
only the first ~8.8MB of a video regardless of request strategy.
2026-07-13 01:59:28 +03:00
KhooLy
dd7f43b99f Add lossless Nuvio collection compatibility 2026-07-12 18:48:53 +03:00
KhooLy
a0df2534a6 Add TMDB builtin-addon mapping functions
Supports fluxa-desktop's synthetic TMDB metadata addon: a static
manifest (catalog + meta resources only, no stream), a genre-name-to-id
catalog URL builder (popular/discover/search, skip mapped to TMDB's
page param), a rich meta mapper that prefers a resolved imdb id and
picks a title logo from TMDB's /images response, and an episode-list
mapper with still_path thumbnails.
2026-07-12 13:49:40 +03:00
KhooLy
ec5418a295 Don't let a blank incoming poster/logo/background wipe cached art
merge_progress_meta_json only fell back to the existing value when the
incoming field was JSON null, but an empty string (what a progress
write produces when no metadata addon can resolve artwork) counted as
present and permanently overwrote the good value already on disk.
Treat empty strings the same as null so removing addons temporarily
can't permanently poison a continue-watching entry's artwork.
2026-07-12 13:23:36 +03:00
KhooLy
31556a62cb Refine sync and streaming boundaries 2026-07-12 02:47:09 +03:00
KhooLy
8af555f608 chore: format core sources 2026-07-12 01:49:50 +03:00
KhooLy
5b62386516 Prefetch initial Discover catalog pages 2026-07-12 01:43:43 +03:00
KhooLy
b9b690f135 Improve torrent streaming and playback matching 2026-07-12 01:24:11 +03:00
KhooLy
24a0e0dcb2 Add pagination support for discover catalog browsing
Discover only ever fetched the first page (typically 20-50 items) of a
catalog and had no way to request more. Add a discoverPageRequested
action, a fetchDiscoverPage effect, and a dedicated DiscoverPaging
generation key so an in-flight "load more" fetch can't clobber a fresh
query (or vice versa) when the user changes catalog/genre mid-fetch.
2026-07-12 00:53:53 +03:00
KhooLy
323b46d9ae Drop addon name prefix from discover catalog labels
The catalog picker showed "AddonName | Instance: Catalog Name" instead
of just the catalog name, which is redundant once the addon is already
selected via the type/catalog flow.
2026-07-12 00:53:39 +03:00
KhooLy
6e37494135 Treat anime as its own discover content type
anime catalogs were collapsed into "series" during type normalization,
but discover_catalog_options_json compared the raw requested type
against the normalized catalog type, so selecting an "Anime" tab never
matched any catalog. Give anime its own bucket and normalize both
sides of the comparison the same way.
2026-07-12 00:53:25 +03:00
KhooLy
674a977d29 Preserve Nuvio collection sources 2026-07-11 14:51:24 +03:00
KhooLy
b11c8ad2c5 Add anime-skip.com timestamp parsing
Derives skip segments from anime-skip.com's point-in-time timestamps
by pairing each recognized-type timestamp with the next one, unlike
AniSkip/IntroDB which already provide start/end intervals.
2026-07-11 14:36:09 +03:00
KhooLy
f7920e3d13 Track per-addon stream failures so the UI can surface them instead of silently dropping results
Adds detail.failedAddons, populated from the fetchDetailStreams
completion payload, distinct from availableAddons so a permanently
failed addon (exhausted retries, bad response, etc.) can be shown to
the user as a failure rather than looking identical to "no streams".
2026-07-11 13:19:22 +03:00
KhooLy
17e7eaee6d Don't discard in-flight stream results on a redundant duplicate request
Re-requesting streams for the same episode while a fetch is already
running used to bump the generation counter and orphan any addon
results still on the way, so a duplicate click could make a fully
valid addon result silently disappear. Now a duplicate request for the
same content is ignored while one is already in flight, and the
generation is only advanced when the request actually targets
different content.
2026-07-11 13:00:55 +03:00
KhooLy
ae2a662a12 Drop stale detail-stream results from an abandoned episode selection
detailStreamsAppended merged partial addon results as soon as they
arrived, gated only on is_loading_streams. Switching episodes quickly
resets that flag's data but not fast enough to stop a slower addon
response for the previous episode from landing in the new episode's
list. Tag the append with the request generation and drop it if the
active generation has since moved on.
2026-07-11 04:20:32 +03:00
KhooLy
403b413a11 Verify next-episode stream matches requested episode before selecting it
select_next_episode_stream_json picked a stream by bingeGroup equality
(which Torrentio derives from the torrent infoHash alone, so every file
in a season-pack torrent shares one), falling back to the first stream
in the list with no title/filename check either way. A duplicate or
mis-scoped addon entry could win and play the wrong episode.
2026-07-11 04:20:26 +03:00
KhooLy
5834189d90 Fix inverted released_only filter in next-episode resolution
resolve_next_episode_json's released_only flag was backwards
(released_only || is_episode_released instead of !released_only ||
...), so the "raw next episode" and "released-only next episode"
call sites got each other's behavior. Also add debug timing around
torrent server teardown/bootstrap to help diagnose slow episode
transitions.
2026-07-11 04:19:30 +03:00
KhooLy
12fc4398e6 Fix core CI checks 2026-07-11 00:03:03 +03:00
KhooLy
85dcc014eb Preserve home data during refresh 2026-07-10 23:57:06 +03:00
KhooLy
194cb4cff3 Always include collection focus GIFs 2026-07-10 23:51:53 +03:00
KhooLy
cf9b0cbe06 Add subtitle delay alignment policy 2026-07-10 23:23:58 +03:00
KhooLy
8ce31e7214 Preserve selected stream provider on completion 2026-07-10 20:22:49 +03:00
KhooLy
05b60e1da4 Avoid repeated torrent file prioritization 2026-07-10 19:41:35 +03:00
KhooLy
c1ef70c9e2 Scope readiness-poll torrent adds to the requested file
The stat-polling "get" action registered a magnet without narrowing
rqbit's only_files to the target index, so progress/readiness could be
driven by unrelated files in the torrent. That let isPlayableEnough
report true before the actual file had any data, handing mpv a stream
URL that wasn't ready yet (Sentry RUST-4). Also re-scope on repeat
lookups of an already-known link so switching files on a shared magnet
(season packs) doesn't keep the first file's scope.
2026-07-10 17:25:20 +03:00
KhooLy
5c07852af7 Wire torrentStatusInfo into the FFI dispatch table
torrent_status_info_json (isPlayableEnough/statusKey/bufferProgress)
existed unreachable from any binding — no ffi.rs arm called it. It's
exactly the policy fluxa-desktop needs to decide when a torrent stream
is actually safe to hand to the player.
2026-07-10 16:39:18 +03:00
KhooLy
e7012c643d Track catalog fetch as a distinct loading state in Discover
is_loading only covered the results fetch, so the frontend had no way
to tell "catalogs still loading" apart from "no catalogs available",
causing a false empty state while addon catalogs were still in flight.
2026-07-10 16:22:28 +03:00
KhooLy
c2b0e610ec Plan Discover per selected catalog 2026-07-10 13:53:39 +03:00
KhooLy
83df69a7dd Stop excluding catalogs from discover when they lack declared genre support
Excluding a catalog entirely whenever it didn't declare genre as a supported
extra was too strict: once genre filtering actually started reaching the
core (previous commit), any user whose installed addons don't declare genre
support at all got zero catalogs and "No content found." Instead, only skip
sending the genre query param to catalogs that don't support it — they still
contribute their normal item list, which then goes through the existing
post-fetch genre filter in discover_sort_plan_json (checks each item's own
genres tag), so mismatched items still get filtered out without starving
the catalog list entirely.

Also switched to the canonical, case-insensitive catalog_supports_extra in
addon_protocol.rs instead of the ad-hoc case-sensitive duplicate added
earlier, matching the pattern already used by search_plan.rs.
2026-07-10 13:28:32 +03:00
KhooLy
913ac0fdcd Exclude catalogs without genre support from genre-filtered discover requests
Discover was sending genre=X to every installed catalog regardless of
whether its manifest declared genre as a supported extra. Addons commonly
ignore unsupported extras and return their default/unfiltered list, so
picking e.g. "Animation" pulled in unrelated items from catalogs (Action,
Sport, ...) that don't actually support genre filtering.
2026-07-10 13:12:41 +03:00
KhooLy
b2c9c4f439 Save outgoing episode progress atomically with playerLoadStreamsRequested
Episode auto-advance (playerLoadStreamsRequested) was fully decoupled
from progress saving: nothing in the core persisted the outgoing
episode's position/metadata before switching, so it relied entirely on
the host remembering to fire a separate savePlaybackProgressRequested
dispatch first. If the host missed that or the app was killed in the
gap between the two dispatches, the last watched position (and its
season/episode metadata) for the finished episode was lost.

playerLoadStreamsRequested now accepts an optional outgoingProgress
payload; when present and the video id is actually changing,
dispatch_load_streams emits a writePlaybackProgress effect for the
outgoing video in the same response as the stream-load effects, so a
single host dispatch covers both.
2026-07-10 12:29:50 +03:00
KhooLy
766d988d67 Fix continue-watching progress merge losing episode season/number
lastEpisodeSeason and lastEpisodeNumber were only taken from the
incoming write when the video changed, with no fallback to the
existing value, same flaw as 54e5fff fixed for name/thumbnail. If
an auto-advanced episode's metadata was incomplete, this nulled out
season/number on the continue-watching record, which downstream
consumers then displayed as "Season 0".
2026-07-10 12:19:15 +03:00
KhooLy
4f90c99117 chore: format Rust sources 2026-07-10 11:18:01 +03:00
KhooLy
b196e61f48 Add Anilist auth provider to profile token merge 2026-07-10 11:11:41 +03:00
KhooLy
7ac59c9bc9 Update README links to FluxaMedia org repos 2026-07-10 11:11:28 +03:00
KhooLy
da3a45a6d5 Ignore AGENTS.md 2026-07-10 11:11:18 +03:00
KhooLy
54e5fffe17 Fix continue-watching progress merge losing episode name/thumbnail
lastEpisodeName and lastEpisodeThumbnail were only taken from the
incoming write when the video changed, with no fallback to the
existing value (unlike continueWatchingPoster/Background). If an
auto-advanced episode's metadata was incomplete, this nulled out a
previously-good title/thumbnail on the continue-watching card.
2026-07-10 02:40:42 +03:00
KhooLy
c5a98f0473 Fire continue-watching badge refresh in parallel with home bootstrap
dispatch_load now also fires RefreshContinueWatching alongside
ReadHomeBootstrap, so continueWatching badges (new episode / up next)
can arrive and update state independently instead of only ever coming
from bootstrap.

Note: fluxa-desktop's readHomeBootstrap still computes badges inline
via refreshReleasedContinueWatching, so home load doesn't yet get
faster from this alone — it currently just computes badges twice.
Realizing the perf win needs a follow-up to drop that inline call and
rely solely on the parallel refresh effect.
2026-07-09 21:29:52 +03:00
KhooLy
69e4c0974c Don't filter anime discover results by native item type
"anime" is a virtual content-type bucket in Discover that doesn't map
to Stremio's native movie/series type field, so items from
anime-flagged catalogs were being dropped when their underlying type
didn't literally equal "anime". Treat it like the empty-filter case.
2026-07-09 21:29:41 +03:00