35 Commits
Author SHA1 Message Date
edde746 e6be5f9fef fix(player): surface a persistent HTTP 503 at open instead of retrying forever
ffmpeg's reconnect loop deliberately retries 503 without bound (#1520), so a
server that keeps refusing the stream at open time left a silent black screen:
ExoPlayer fell back to MPV, MPV reconnected forever, and no error ever reached
the screen. A new open-phase watchdog arms on the first 503 seen before any
frame renders and, after 20s without one, synthesizes a server-http-503 error
that shows an actionable dialog. Mid-stream 503s and live TV keep their
existing ride-out paths.

close #1830
2026-08-08 12:06:32 +02:00
edde746 1b6a811c07 fix(delete): name the delete target and verify what its files back
"Delete from server" read identically for an episode, a season and a
whole show: same menu label, same dialog title, same red button, and a
body that named nothing. The menu header did not disambiguate either,
because MediaItem.displayTitle collapses an episode to its show name.
A reporter deleted a whole series from the detail hero's ⋮ believing it
acted on the episode he had highlighted, and the confirmation gave him
nothing to catch it with. Every one of those strings now names the kind,
and the body names the exact item — show, season and episode number, and
episode title.

Deleting a single item also destroyed files the confirmation never
mentioned: a Plex multi-episode file (S01E01-E03.mkv) takes its other
episodes with it, and a split item takes every part. The dialog now
reports that up front and, on success, emits deletion events for the
siblings the server destroyed so their rows do not linger.

The scope behind that warning is only asserted when it is established.
MediaItem.allPartFiles drops parts with no path, so a non-empty set
proves nothing about the ones it filtered out; a version is trusted only
when every part reports a file. A browse row that omits paths is missing
evidence rather than proof of a distinct file, so both the target and
each candidate sibling fall back to the detail endpoint before any
conclusion — otherwise a thin row, including the file-less part
PlexMappers fabricates for an empty payload, would look like a server
that withholds paths. When the answer cannot be established the dialog
says so in an error-tinted block and its button reads "Delete anyway",
separating a transient probe failure from a server that never sends
paths. It deliberately does not refuse: Plex withholds paths from
restricted users the server itself authorizes to delete, so failing
closed would take the feature away from them permanently.

Probing a season stays bounded in both directions. Siblings resolve one
at a time, so a season of thin rows cannot fan out a detail request per
episode, and expiry cancels the walk rather than merely abandoning it —
`Future.timeout` completes the future the caller awaits but leaves the
work behind it running, which would resume on the next sibling once the
outstanding request answered. A cooperative flag is checked before each
lookup, so at most the one already in flight outlives the deadline; the
neutral client exposes no abort handle for item lookups, so that one
cannot be recalled.

The spinner covering the probe was only barrierDismissible, which does
not stop system back. Back dismissed it and the cleanup pop then closed
the screen underneath, dropping the user out of the detail page
mid-flow. It now traps back, matching the non-dismissible contract its
own doc claims, which also repairs the log uploader and the file-info
sheet.

Coverage splits by what each layer owns. The dialog, its copy and the
DELETE wiring are backend-neutral and stay in the menu widget tests.
Plex — the backend multi-episode files actually come from — gets the
resolver over a real PlexClient and a mocked transport: a row with no
media at all, scope recovered from /library/metadata/{id}, siblings and
paths from /children, a Part that names no file, a sibling whose path
never resolves, the request count a sixty-episode thin season may cost,
and the rating key the DELETE carries. Those are plain async tests
because the Plex metadata cache is a real database whose I/O the widget
tester's fake clock never drives. Deadline behaviour needs the opposite,
so it is pinned separately under fakeAsync against a gated fake client,
with no wall-clock waiting anywhere.

close #1781
2026-08-06 03:45:08 +02:00
edde746 86c8011b72 fix(player): tell the user when the server cannot read the media file
A 404 on the media stream means the server resolved the item but could not
open the file behind it — moved, deleted, or on storage that went away.
Jellyfin maps the resulting FileNotFoundException to 404, and PlaybackInfo
never stats the file, so negotiation succeeds and only the stream request
fails. Playback then died with a snackbar reading "Failed to open
[REDACTED_URL]" before popping the route, which tells the user nothing and
leaves nothing useful in a bug report.

Generalize the HTTP-500 log probe into PlayerError.httpStatusFromLog and
latch every status in fatalPlaybackHttpStatuses. Each latches on its own so
the 503 that stream-lavf-o deliberately retries cannot mask the fatal status
behind it. A 404 now raises a dedicated modal naming the cause and the fix.

On Android a 404 previously failed the "Response code: 500" string test and
fell through to the ExoPlayer→MPV fallback, showing "switching to compatible
player" before failing again on the same request. Read the real status off
HttpDataSource.InvalidResponseCodeException instead and skip the fallback:
an HTTP status is not a codec problem.
2026-07-31 21:45:33 +02:00
edde746 71735354b9 fix(tvos): unify remote and text input ownership 2026-07-26 07:08:01 +02:00
edde746 e4db04fa62 fix: align UI focus and sheet behavior 2026-07-13 11:28:32 +02:00
edde746 2101f1d7f0 perf: isolate focus and media rebuilds 2026-07-12 19:00:11 +02:00
edde746 a481fc1000 refactor(dialogs): unify text input contract 2026-07-12 08:42:26 +02:00
edde746 d5c575cbfe fix: keep download toggle label on one line 2026-06-26 01:19:38 +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 6f39a286d3 fix(profiles): remount profile session
close #1371
2026-06-18 00:53:48 +02:00
edde746 09f5121732 feat(metadata): add Jellyfin edit support 2026-06-01 08:34:38 +02:00
edde746 a931d5910b fix(tv): let text fields fall back to directional traversal
close #1176
2026-05-29 03:30:54 +02:00
edde746 955c75db60 fix(nav): improve back exit behavior 2026-05-07 03:06:04 +02:00
edde746 7988c83bec fix: improve d-pad focus navigation 2026-05-07 00:27:58 +02:00
edde746 ed4be7b96d refactor: strip obvious comments 2026-05-04 22:40:18 +02:00
edde746 876a44b244 feat(tvos): add virtual keyboard input 2026-05-03 01:32:58 +02:00
edde746 244b291255 refactor: share controller disposal and Plex DTO parsing 2026-05-02 09:43:08 +02:00
edde746 e4e8299981 refactor: showLoadingDialog helper, json_serializable for plex_home 2026-04-25 03:25:09 +02:00
edde746 c10cf55f2d feat: transcoding 2026-04-22 21:07:12 +02:00
edde746 5937e5c721 refactor: extract shared mixins and helpers to dedupe 2026-04-20 21:25:50 +02:00
edde746 3da51f9d64 chore: pre-commit ci hook, dart format 2026-04-18 12:40:35 +02:00
edde746 1e62c6c756 feat: bandwidth limit modal on stream 500 2026-04-17 13:12:07 +02:00
edde746 7f237c9c7b refactor: reuse showTextInputDialog for episode count input 2026-04-08 18:08:39 +02:00
edde746 8e7431e7e5 feat: download options picker for series/seasons 2026-04-07 22:59:35 +02:00
edde746 fc7892daed fix: option picker dialog focus for TV/keyboard nav 2026-04-01 06:30:08 +02:00
edde746 9e0c3e7b68 fix: improve delete-from-server button safety
Rename "Delete" to "Delete from server" with distinct icon and
always-red styling to prevent accidental server deletions.

Closes #568
2026-02-27 14:49:47 +01:00
edde746 4eb55885f7 feat: metadata editing
close #338
2026-02-22 17:56:13 +01:00
edde746 7f8d4c7946 fix: dpad focus for dialogs and buttons 2026-02-22 14:50:59 +01:00
edde746 53c94b14cc feat: add back button exit confirmation on Android TV
close #467
2026-02-16 01:22:48 +01:00
edde746 04438c8e45 refactor: code quality & format 2026-02-15 04:07:09 +01:00
Micah Morrison 336da4c110 Fix the colors of the delete buttons 2026-02-13 20:07:07 -05:00
edde746 2a1c99ac06 fix: dialog autofocus and button sizing 2026-02-08 10:30:14 +01:00
edde746 3c89d0eba8 refactor: update formatting 2025-12-15 02:29:03 +01:00
edde746 a2a5a5ae55 refactor: simplify & deduplicate 2025-12-14 21:03:11 +01:00
edde746 a952ab0a00 refactor: deduplicate & other code quality fixes 2025-11-16 22:42:04 +01:00