Commit Graph
184 Commits
Author SHA1 Message Date
edde746 7f0cad339c fix(startup): report and repair a failed launch instead of showing "Error"
Since 2.10.0 the whole app sits behind one all-or-nothing initialization
gate, and that gate discarded the only evidence of its own failure. It
caught the error, logged nothing but `error.runtimeType`, rendered an
icon plus the word "Error" plus Retry, and never reported the error
because catching it kept the crash reporter from ever seeing it. There
is no log file on any platform, the buffer is in memory only, a
double-clicked Windows release build has no console, and the log viewer
lives in Settings, behind the gate that just failed. #1732 is the result:
a Windows 11 user whose app will not boot and who cannot produce a single
byte of diagnostic detail.

The gate now names its phases. Each step is wrapped so a throw carries
the phase it came from, replacing a `Future.wait` that discarded every
error but the first and could not attribute it to any of four concurrent
steps. The failure screen renders the phase, the exception type, the
message and an expandable stack, plus copy and upload actions that reuse
the existing log-relay flow. The record is persisted next to the database
so the next successful launch can surface it in Settings > Logs, and it
is reported to the crash reporter explicitly.

Only preferences and the database still gate the launch. Window chrome,
locale, crash-reporting init, TV/performance detection, the image-cache
budget and download storage are best-effort and time-bounded, so a
stalled platform thread degrades instead of holding the splash forever.
Sentry no longer receives the startup work as its `appRunner`: that made
a startup failure indistinguishable from a Sentry failure, and the guard
would then have re-run migrations and the database open a second time.

The two remaining fatal steps become recoverable. Preference reads
tolerate a value whose stored type no longer matches, dropping the key
and defaulting instead of failing the boot. A store that cannot be parsed
is detected before either desktop plugin backend can memoise it, which is
what makes an in-process repair possible at all. Repair is never
automatic: it states what it will cost, salvages the credential-vault key
and every tracker and Seerr session it can validate out of the damaged
bytes, reseeds them, and moves the original aside rather than deleting
it. Servers and profiles survive a salvaged key because their tokens are
ciphertext in the database; tracker and Seerr sessions are plaintext
preference entries, so the copy says they may still need reconnecting.

Nothing derived from the store reaches a diagnostic. `FormatException`
prints an excerpt of whatever it failed to parse, and during startup that
document holds the vault key, refresh tokens and session cookies while
the redaction manager still has nothing registered, so the wrapper keeps
only the cause's type and offset and the record is an allowlist of
already-redacted fields. The quarantined copy is labelled as containing
credentials, is never offered for upload, and can be deleted from the
dialog.

Also self-heals orphaned WAL/SHM sidecars on desktop rather than only
tvOS, makes every `createTable` migration step idempotent, keeps MSVC
link by-products out of the Windows bundle, and asserts bundle contents
in CI.

Refs #1732
2026-07-31 21:45:32 +02:00
edde746 27b994422c feat(player): optionally follow the server's per-episode track selections
With the new playback setting enabled, episode advance carries no audio or
subtitle preference at all, so both resolve from the streams selected on
the server for each individual episode. This serves setups that curate
selections server-side (e.g. Plex Auto Languages) and is independent of
"Remember track selections", which keeps gating only the write-back of
manual changes.

close #1717
2026-07-30 02:52:18 +02:00
edde746 53288116fe fix(explore): size catalog detail relations, ratings and facts to their content
Four sections of the catalog detail screen spent more room than their data
justified.

Franchise relations drew one hub shelf per label. Real payloads make that
absurd: MAL returns twelve relations for Attack on Titan across six labels,
and "Side story" and "Sequel" each hold exactly one title, so each spent a
header, a scroll row and one card. Flatten the labelled groups into one
"Related titles" section of compact rows — poster thumb, label, title and year
— that flow into columns on wide viewports. D-pad moves through the grid by
index and still hands off to the cast strip above and the recommendations
shelf below, which keeps its shelf because taste-based recommendations are
meant to be browsed.

Drop the MAL picture gallery. It was a horizontal strip of unfocusable poster
variants of the title you are already looking at, and it cost a page-height of
scroll; the `pictures` field comes back out of the detail request with it.

Draw attributed scores behind their own brand mark where the source has one,
the way the media detail screen already does: Rotten Tomatoes fresh/rotten and
upright/spilled, IMDb and TMDB, each on the scale that source publishes.
Sources with no mark (critic, audience, tracker scores) keep their written
label. Plex's own badge state is derived from the 60% tomatometer threshold it
encodes in `image.rating.ripe`.

Flow the definition rows — original title, studios, country, budget, box
office, crew — into two or three columns once the window is wide enough.
A 1440-wide window drew a 140-pixel label, a short value and 1,000 pixels of
nothing per fact.

Verified against live MAL and Plex Discover payloads on macOS: the Attack on
Titan page drops from 4,082 to 2,115 logical pixels, Dune: Part Two from 1,282
to 1,154.
2026-07-30 01:01:38 +02:00
edde746 27acbaf435 feat(explore): surface the catalog data providers already return
Explore shelf cards drew a poster, a title and a year. An audit of all six
catalog sources found the rest was lost at two boundaries — the wire-to-DTO
mapping and the DTO-to-CatalogItem mapping — and then simply not drawn: the
grid card fell through every branch of buildMetadataSubtitle to the year-only
case, while the list card used by search already composed certification,
runtime and rating from fields the synthesized MediaItem already held.

Extend CatalogItem with the neutral facts every provider had been dropping:
attributed rating sources, leaderboard ranks that keep their season window,
audience counters that keep their timeframe, broadcast slots, next-episode air
times, server availability and request state, exact release dates, alternate
titles, format, source material, studios, countries, languages, credits, tags,
links, artwork variants, play state, gallery art and background prose. Replace
fetchCast and fetchRelated with one fetchDetail returning the enriched item,
its cast, its recommendations and labelled franchise relations without adding
a request: sources needing two calls keep two and run them concurrently with
isolated failures.

Map those fields in all six sources, widening only field selections that cost
no extra round trip — MAL's fields list, AniList's selection set and a bounded
row cast that lets detail skip its character call, Trakt's guest stars, Seerr's
language parameter and TMDB size ladder, and Plex's includeUserState. Plex hub
artwork widens only on TV, where the spotlight is its only consumer, because it
doubles the payload.

Render them: a rating-first caption and bounded badges on the shelf card,
labelled sections on the detail screen, provider hub styles and result counts
on shelves, and logo, banner and accent art in the TV spotlight.

Verified against live Plex, AniList, Simkl and MAL responses, and on a Pixel 7.
2026-07-29 06:47:54 +02:00
edde746 0ef71e6489 feat(file-info): detail every version, file and stream the server reports
The sheet collapsed an item to `Media.first` / `MediaSources.first` and
rendered a fixed set of rows, so split files, extra versions, per-track
properties, HDR classification and Dolby Vision were all invisible.

Model the payload the way both servers shape it — versions own parts,
parts own streams — and project every property either backend populates
onto `MediaStreamDetails`. The field set comes from sweeping both test
servers in full through the clients' own request shapes (Plex 6842
Media / 6842 Part / 39864 Stream entries, Jellyfin 6349 sources / 37763
streams / 61224 attachments), so file presence, Dolby Vision layers,
dynamic range, sample rate, spatial audio, sidecar provenance, embedded
attachments, rotation and the lyric stream type all survive. A coverage
test fails when a server key is neither carried, folded into a sibling,
nor excluded with a reason.

File Info also stopped trusting the shared `/library/metadata/{id}`
cache row: `getPlaybackExtras` writes it without `includeStreams` /
`checkFiles`, so the sheet could render with no stream table at all.
Detect that shape and refetch once under the request context captured
before the cache read, so the outgoing token and the cache namespace
stay on one profile.

Rework the layout to match: a summary chip row, then flat tonal cards
with a two-column field grid that collapses to one column on narrow
viewports, per-stream cards with flag chips, and a copyable monospace
path row. The card fill is a tonal step off the text colour rather than
the `bg` token, which is one shade from the sheet surface on OLED.
2026-07-29 05:19:27 +02:00
edde746 f3795d49eb feat(player): answer transport keys with transient indicators, not the chrome
Pressing pause or seeking while the player's on-screen controls were hidden raised
the entire OSD, covering the subtitles the viewer was rewinding to read. Transport
keys now answer with a transient indicator and leave the chrome down; Select,
D-pad Center and a centre tap remain the deliberate way to bring the controls
back.

Play/pause confirms with an icon-only translucent disc at the centre of the frame,
72px around a 44px glyph, which grows and fades in, holds half a second at rest,
then runs the same motion in reverse. Seeking shows the amount plus a single
chevron on the same line at the edge it travels toward, with no backdrop at all:
anything large enough to read as a surface is large enough to cover picture and
subtitles, so legibility comes from shadows instead. Only the chevron moves, and
it eases outward across most of its cycle and returns briefly, holding a visible
opacity floor rather than blinking out. Type is scaled per platform, since a
television is read from across the room. The existing text pill stays for genuine
notices - rate changes, chapter titles, zoom, errors - because an earlier centred
pill overlapped ASS \an8 subtitle placement, which is the readability complaint
this feedback exists to answer.

Every relative seek entry point now shares one coalescing primitive. The keyboard
shortcuts fell through to KeyboardShortcutsService and previously reported
nothing, and both they and the remote's chapter fallback rebased each press off
player.state.position, so a burst against a slow backend pinned every request near
one step while the indicator climbed to a total that was never committed. A
released key commits its pending target immediately and resets the acceleration
tier, including on live TV where seeks bypass the accumulator. A chapter seek with
nowhere to go, past the last chapter or already at the start, no longer announces a
jump it does not perform.

Rewind-on-resume follows the resolved intent rather than the current state, so a
directed pause on an already-paused video neither resumes nor rewinds. Indicators
carry their own liveRegion semantics nodes: their labels previously merged into the
full-screen "show playback controls" target, corrupting its accessible name, and
they keep announcing "Paused"/"Playing" and the seek amount from icon-only visuals.

close #1676
2026-07-29 04:24:04 +02:00
edde746 ef310459e5 fix(i18n): drop unused video-control strings and restore spinner lookups
`clean_translations.py --strict` reported seven unused keys.

Three are genuinely dead: TrackSelectionHelper.getEmptyMessage was
removed as unreachable in 4307c49c, and the sheets that need an empty
state carry their own strings. Removes them from every locale.

The four accessibility keys are false positives. tv_number_spinner
aliased the subtree as `final a11y = ...accessibility`, which the static
scanner cannot follow — its docstring says as much. Binds the documented
`final t = Translations.of(context)` instead and hoists the two labels,
so the semantics stay identical and the scanner sees the chains.
2026-07-26 23:05:53 +02:00
edde746 6c14049e95 fix(tvos): make EAC3 playback conform to Dolby's guidance
Groundwork for #1300. Establishes the session, buffering and route
handling Dolby's application guide prescribes, and adds the diagnostic
arm needed to find out whether Apple's sample-buffer renderer can carry
Atmos objects at all.

Audio session, per the guide's sequence:

- Adopt the long-form playback profile in one atomic call at app launch
  and activate the session there. The SDK only accepts that policy with
  category Playback, a Default/MoviePlayback/SpokenAudio mode and no
  options, so it cannot be assembled from separate calls.
- Report the resolved rendering mode in the player, hidden unless the
  system resolves it. Apple only resolves it for CarPlay and AirPlay, so
  an unresolved value means unknown, never "not Dolby".

Diagnostics (Apple TV only, Settings > Video Playback > Atmos Output Test):

- Add a sample-buffer arm. It reads the asset with AVAssetReader at
  outputSettings nil and hands the untouched compressed buffers and the
  untouched format description straight to the renderer, with a variant
  that rebuilds the description the way playback builds it. Every
  existing mode went through AVPlayer, so nothing exercised the path
  playback actually uses; this is what tells us whether the renderer or
  our construction is at fault.
- Add an AirPlay route picker. AirPlay is the only route where the system
  resolves the rendering mode and the supported channel layouts, so it is
  what makes those observations reachable at all, and the AVPlayer arms
  now allow external playback so every arm can be compared on the same
  destination.
- Add a session-mode toggle for the one profile difference between the
  guide and previous playback behaviour.
- Report the session profile, supported layouts, both format
  descriptions, the magic cookie and the renderer status, and release the
  session on stop so a failed run cannot contaminate the next one.

Also bumps MPVKit to 1.0.14, which carries the matching audio output
work: the channel layout AVFoundation itself uses for Dolby content, a
renderer-failure observer so the fallback to PCM can actually run, the
prescribed feed ordering and preroll, flush recovery that re-supplies the
discarded audio instead of shifting later audio into its place, and
capability-driven fallback on route and capability changes.

This does not yet fix #1300. Whether the sample-buffer renderer can carry
JOC is still unknown; it removes every difference from the documented
setup that could explain the failure, and gives us the arm to answer it
on real hardware.
2026-07-26 20:42:58 +02:00
edde746 60cc983471 feat(downloads): remove playlist sync downloads together
close #1656
2026-07-26 14:58:38 +02:00
edde746 1fea9ef6e3 fix(search): recover omitted Plex media categories
close #1598
2026-07-26 14:58:38 +02:00
edde746 e251273322 feat(downloads): warn about Android background restrictions 2026-07-26 04:24:55 +02:00
edde746andEvan J c9543d4af0 feat(media): show directors in detail info rows
Co-authored-by: Evan J <42357644+ejach@users.noreply.github.com>
2026-07-25 22:40:39 +02:00
edde746 6c03094ef1 fix(settings): label image-only cache clearing accurately 2026-07-24 08:09:14 +02:00
edde746 54273ab09c fix(downloads): recover from full storage
close #1655
2026-07-24 07:48:42 +02:00
ad7ef112fc feat(i18n): add Hungarian and Traditional Chinese locales
Complete and revise every shipped locale against the current English source, preserve locale-specific plurals, and map script-specific Chinese locales through device, Intl, duration, and Plex boundaries.

Co-authored-by: emgeje <mgj@mgj.hu>

Co-authored-by: junyou1998 <junyou1998@gmail.com>
2026-07-24 07:30:43 +02:00
edde746 b41fb4fe75 fix(ui): harden settings focus and semantics 2026-07-24 03:46:50 +02:00
edde746 43a8fe020d fix(relay): secure reconnect and room ownership 2026-07-24 03:46:50 +02:00
edde746 e0bf66eea8 fix(runtime): harden application service boundaries 2026-07-24 03:46:46 +02:00
edde746 e32fcc2190 feat(explore): add AniList, Simkl, and Plex catalogs 2026-07-20 10:28:31 +02:00
edde746 a1b6a89714 fix(player): load subtitle sidecars with media
close #1583
2026-07-17 23:09:07 +02:00
edde746 d5f3e581df fix: eliminate cross-app consistency drift 2026-07-13 23:13:53 +02:00
edde746 ec249d2ed4 fix(jellyfin): repair music library browsing
Fixes #1557
2026-07-13 22:58:12 +02:00
edde746 e03edad824 fix: prevent async state and platform regressions 2026-07-13 00:03:52 +02:00
edde746 fe8d405a56 fix(i18n): enforce complete localized coverage 2026-07-12 17:31:14 +02:00
edde746andmoduvoice 5af3a926d7 i18n: complete locale translations
close #1537

Co-authored-by: moduvoice <moduvoicr77@gmail.com>
2026-07-11 22:53:33 +02:00
edde746 c5c7dd614f i18n: translate stream-interrupted snackbar to all locales 2026-07-10 20:27:52 +02:00
edde746 f94b8c6586 feat(settings): Services hub, Seerr sign-in, and all-locale strings 2026-07-10 07:09:29 +02:00
edde746 632d2ec16b feat(livetv): let user pick and remember the recording target library
close #1513
2026-07-10 06:41:15 +02:00
edde746 214a7c13dc feat(music): show full play queue with history, YouTube Music style
Queue sheet/panel now renders the whole playback order — dimmed played
tracks above the current one — instead of only up-next. Opens scrolled
to the current track and follows advancement while parked on it.
2026-07-06 11:30:23 +02:00
edde746 4da489d4dc i18n(music): translate music player strings to all locales 2026-07-05 22:16:36 +02:00
edde746 90c4fe5458 feat(audio): add Kodi-style stereo downmix with center channel boost 2026-07-05 14:29:57 +02:00
edde746 7632d4bea7 feat(jellyfin): favorites library filter + heart favorite in the rate sheet
close #1485
2026-07-04 18:33:58 +02:00
edde746 6d95e35448 feat(tvos): AVPlayer Atmos sink wiring + Atmos output diagnostics
Re-enables Audio Passthrough on Apple TV against the MPVKit EAC3-JOC
AVPlayer sink (pin bump to v1.0.8 follows), adds an AVPlayer test
harness + diagnostics screen for #1300, and makes loudnorm yield while
passthrough is active (fixes a latent spdif conflict on desktop too).
2026-07-02 15:52:10 +02:00
edde746 5f49dddb4d feat(ui): M3E restyle for settings, auth, and profile screens 2026-07-02 11:47:43 +02:00
edde746 44be03d39c refactor(profiles): shared auth/mint flows + screen fixes 2026-07-02 11:41:25 +02:00
edde746 28e6e349b3 fix(android): restore ExoPlayer audio for tunneled decoded PCM 2026-06-30 18:58:08 +02:00
edde746 f04691d321 feat: add "Include Specials" toggle to the show download dialog
The aired-order rework can sweep correctly-placed Specials into "download
next N"; this toggle lets users opt out. Shown only for whole shows
(reusing FocusableSwitchListTile via an optional toggle on the shared
option-picker dialog), remembered across opens via a BoolPref
(default on = unchanged behavior). Filters Specials at the single
collect choke point (_collectPlayable), with a guard so explicitly
downloading the Specials season still queues its episodes.
2026-06-25 23:41:31 +02:00
edde746 7e1d5ad4c4 fix: add Jellyfin episode-added sort
close #1274
2026-06-18 16:22:01 +02:00
edde746 94f2daa22e feat: add episode action setting
close #1351
2026-06-18 11:16:38 +02:00
edde746 c39b5a5cf1 feat(player): show cache limit in perf overlay 2026-06-13 21:21:15 +02:00
edde746 24980aff95 feat(subs): render resolution setting for the avfoundation VO 2026-06-12 21:05:28 +02:00
edde746 587609d7f6 fix(tv): hug poster with focus border and add focus glow toggle
close #1278
2026-06-12 08:50:29 +02:00
edde746 62abd3c30a feat(jellyfin): simpler, code-first Quick Connect screen 2026-06-12 03:16:58 +02:00
edde746 3221a0cb1a feat: add continue watching details navigation
close #1295
2026-06-10 12:50:32 +02:00
edde746 38616770e5 fix(i18n): localize season & episode labels in downloads tree 2026-06-07 21:46:44 +02:00
edde746 9cebcb97c5 feat(plex): add Plays and User Rating sort options
close #1264
2026-06-07 17:50:36 +02:00
edde746 1ef4032666 feat: add startup section setting
close #1240
2026-06-06 13:08:02 +02:00
edde746 4c4b18e2cd fix(i18n): localize hardcoded UI strings
close #1257
2026-06-06 11:45:34 +02:00
edde746 0fd0506551 feat(media): lazily page detail episodes
close #1245
2026-06-05 19:26:02 +02:00
edde746 273f129b68 fix(i18n): remove unsupported newline guidance 2026-06-01 08:34:38 +02:00