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.
Torrentio and similar addons were sometimes missing from stream results
because a single transient network blip or slow response past the
timeout permanently dropped that addon's results with no retry.
Pairs with fluxa-core ae2a662: passes the effect's generation into
fetchDetailStreams so its partial-result callback can tell the core
which episode selection it belongs to, letting the core drop appends
from an abandoned episode's still-in-flight addon queries.
Threads the requested episode's id into coreSelectNextEpisodeStream so
the core can verify a candidate stream's title/filename actually
matches before selecting it, instead of trusting bingeGroup/list-order
alone (fluxa-core 403b413).
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.
firstFrameFiredRef never reset between episodes, so the loading
overlay's dismiss path was permanently disabled after the first
episode played. Also await mpv's stop command before tearing down
the previous episode's torrent session, and keep meta.videos in
sync with the background-fetched episode list so the next-episode
and episode-panel fallbacks don't read a stale/empty list.
Progress saves land in two steps: an immediate save with just lastVideoId
and season/episode, then a follow-up save once episode title/show name
metadata resolves. If a Continue Watching refresh landed between those two
saves, mergeContinueWatching would replace the row wholesale with the
name-less version and it stayed that way until another refresh happened to
fire. Backfill name/lastEpisodeName from the previous item when the
incoming one is missing them.
readCalendarMonth built localItems by calling coreCalendarItemsFromMeta on
each raw watchlist/continueWatching item, but that Rust function only
produces entries from a meta.videos array embedded in the item -- nothing
in the codebase ever stores a videos array on a library item. The actual
upcoming-episode data lives in item.nextEpisodeAirDate, populated by
refreshWatchlistAirDates, but readCalendarMonth never read that field.
This meant localItems was silently empty for every user regardless of
where the item came from (local, Trakt, Simkl, Stremio, Nuvio, etc.),
even once air dates were correctly fetched.
Build localItems directly from nextEpisodeAirDate instead.
Simkl exposes the same calendar/shows + calendar/movies personal
endpoints Trakt does; AniList's existing MediaListCollection query now
also asks for nextAiringEpisode. refreshExternalCalendarItems merges
whichever of the three the user has connected into
lib.externalCalendarItems.
Stremio and Nuvio don't have a calendar concept of their own — both
already merge their continue-watching/watchlist into lib.watchlist and
lib.continueWatching directly, so those items already flow through the
existing local addon-based air-date path without extra wiring.
Calendar's externalItems slot existed in the data model and UI but was
never fed by anything — Trakt sync only mirrored watchlist/watched/
progress, never queried Trakt's calendars/my/shows and calendars/my/movies
endpoints. Fetch those on calendar load and store into
lib.externalCalendarItems, which the screen already renders.
discover.catalogs held whatever type the last fetch returned, so
switching content type raced the async refetch: the auto-select
effect could latch onto a stale catalog key from the previous type,
which then matched nothing once the real catalogs arrived. Filter
catalogs by the current contentType at the source so a mismatched
entry can never be selected.
Adds a Vitest + RTL harness (none existed) to drive the real
DiscoverScreen through the exact race condition.
Changing season cleared selectedEpisode, which caused the episode-plan
refetch to fall back to lastVideoId (last-watched episode) and silently
reset selectedSeason to that episode's season once the async plan
resolved.
The modern tabbed detail layout initialized activeTab to 'related' for
non-series items, so opening a movie's detail screen landed on Similar
Titles instead of the overview/details content.
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.
The bare Upscale_CNN shader only did anything when the output was
larger than the input, so toggling Anime4K at native resolution
looked like a no-op. Each quality tier now runs Clamp_Highlights ->
Restore -> Upscale -> AutoDownscalePre -> Upscale -> Thin, matching
the standard Anime4K mode presets, so restore/thin stages are visible
even at 1:1 while the downscale/second-upscale stages self-disable
via their WHEN gates when there's no resolution headroom.
Frame interpolation could never engage: vo_libmpv answers no
VOCTRL_GET_DISPLAY_FPS and vsync estimation only accumulates once
display-sync is already active, so with the render API
video-sync=display-resample silently reverts unless the client sets
display-fps-override. Set a 60 FPS baseline at renderer init and
override it with the real monitor refresh rate (GDK monitor on Linux,
GetDeviceCaps VREFRESH on Windows); mpv refines it further from
report_swap timing. Turning interpolation off now also resets
video-sync back to audio.
Anime4K: drop the //!WHEN OUTPUT > 1.2x gates from the bundled shaders
(they made the shader a no-op whenever the window wasn't upscaling by
more than 1.2x) and deliver glsl-shaders changes via change-list
commands. Verified against the bundled libmpv fork with a headless
EGL harness: shader set/clr visibly changes rendered pixels and
display-sync-active flips to yes once an fps override exists.
player_init now creates the mpv renderer even when a native surface is
used, so the first session's player_apply_preferences no longer lands
on a missing renderer and gets dropped. Async mpv command failures are
logged from MPV_EVENT_COMMAND_REPLY instead of vanishing.
Downloaded torrent pieces/files were written to the app data dir and
never removed, so every torrent ever streamed accumulated on disk
forever (users reported multi-GB AppData folders). torrent-cache is a
transient streaming scratch buffer with no other consumer, so wipe it
whenever no stream is active and sweep it again at app launch to catch
anything left behind by a crash or force-quit.